CollectionAlarmWriter.java 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. package com.zksy.api.service;
  2. import com.zksy.api.domain.AlarmData;
  3. import com.zksy.api.mapper.AlarmDataMapper;
  4. import com.zksy.api.mapper.AlarmLogMapper;
  5. import com.zksy.api.enums.AlarmLogOperTypeEnum;
  6. import com.zksy.api.support.AlarmLifecycleSupport;
  7. import lombok.extern.slf4j.Slf4j;
  8. import org.springframework.stereotype.Service;
  9. import org.springframework.transaction.PlatformTransactionManager;
  10. import org.springframework.transaction.TransactionDefinition;
  11. import org.springframework.transaction.support.TransactionTemplate;
  12. import java.time.LocalDateTime;
  13. import java.util.List;
  14. import java.util.Objects;
  15. /**
  16. * 采集告警及离线告警的统一入库规则;告警处理状态与设备实时报警状态互相独立。
  17. */
  18. @Slf4j
  19. @Service
  20. public class CollectionAlarmWriter {
  21. private final AlarmDataMapper mapper;
  22. private final AlarmLogMapper logMapper;
  23. private final TransactionTemplate transaction;
  24. public CollectionAlarmWriter(AlarmDataMapper mapper, AlarmLogMapper logMapper, PlatformTransactionManager transactionManager) {
  25. this.mapper = mapper;
  26. this.logMapper = logMapper;
  27. transaction = new TransactionTemplate(transactionManager);
  28. transaction.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
  29. transaction.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
  30. transaction.setTimeout(10);
  31. }
  32. /**
  33. * 已有未处理告警时,同级跳过,等级升降则更新原记录及对应采集值。
  34. *
  35. * @return true表示新增,false表示复用原记录(更新或跳过);真实入库失败抛出异常。
  36. */
  37. public boolean saveOrUpdateUnhandled(AlarmData alarm) {
  38. if (alarm == null) throw new IllegalArgumentException("告警不能为空");
  39. validateKey(alarm.getDeviceCode(), alarm.getWarningCode());
  40. String warningName = firstNonBlank(mapper.selectWarningName(alarm.getWarningCode()),
  41. alarm.getWarningCode(), "监测报警");
  42. alarm.setAlarmContent(AlarmLifecycleSupport.alarmContent(warningName,
  43. alarm.getActualValue(), alarm.getMinValue(), alarm.getMaxValue(),
  44. mapper.selectWarningUnit(alarm.getWarningCode())));
  45. if (alarm.getCreateTime() == null) alarm.setCreateTime(LocalDateTime.now());
  46. if (alarm.getAlarmStatus() == null) alarm.setAlarmStatus(0);
  47. return Boolean.TRUE.equals(transaction.execute(status -> {
  48. lockKey(alarm.getDeviceCode(), alarm.getWarningCode());
  49. AlarmData existing = mapper.selectUnhandledAlarmForUpdate(alarm.getDeviceCode(), alarm.getWarningCode());
  50. if (existing != null) {
  51. // 缺失等级的报文不能清除已有有效等级;真实采集端提供1-4级。
  52. if (alarm.getAlarmLevel() != null && !Objects.equals(existing.getAlarmLevel(), alarm.getAlarmLevel())) {
  53. if (mapper.updateUnhandledAlarmLevel(existing.getId(), alarm) != 1) {
  54. throw new IllegalStateException("未处理告警等级更新未成功");
  55. }
  56. log.info("更新未处理告警等级: deviceCode={}, warningCode={}, alarmId={}, level={}->{}",
  57. alarm.getDeviceCode(), alarm.getWarningCode(), existing.getId(), existing.getAlarmLevel(), alarm.getAlarmLevel());
  58. } else {
  59. log.debug("跳过同级未处理告警: deviceCode={}, warningCode={}, existingAlarmId={}",
  60. alarm.getDeviceCode(), alarm.getWarningCode(), existing.getId());
  61. }
  62. return false;
  63. }
  64. // 新记录由程序赋号;复用/恢复已有记录不会生成或修改号码,不信任请求带入的号码。
  65. if (!Integer.valueOf(1).equals(mapper.lockAlarmNumberGeneration())) {
  66. throw new IllegalStateException("未取得报警单号生成事务锁");
  67. }
  68. alarm.setAlarmNo(AlarmLifecycleSupport.nextAlarmNo(mapper::existsAlarmNo));
  69. if (mapper.insert(alarm) != 1) throw new IllegalStateException("告警入库未成功");
  70. writeLifecycleLog(alarm.getId(), AlarmLogOperTypeEnum.CREATED, alarm.getAlarmContent(), "监测系统", alarm.getCreateTime(), 0L);
  71. return true;
  72. }));
  73. }
  74. private String firstNonBlank(String... values) {
  75. for (String value : values) {
  76. if (value != null && !value.trim().isEmpty()) return value.trim();
  77. }
  78. return "监测报警";
  79. }
  80. /**
  81. * 有效恢复数据才调用;0行表示无待处理告警,重复正常上报不会改写历史处理信息。
  82. */
  83. public int markRecovered(String deviceCode, String warningCode) {
  84. validateKey(deviceCode, warningCode);
  85. return transaction.execute(status -> {
  86. lockKey(deviceCode, warningCode);
  87. List<String> alarmIds = mapper.selectRecoverableAlarmIdsForUpdate(deviceCode, warningCode);
  88. for (String alarmId : alarmIds) {
  89. long beforeLogId = logMapper.latestLogId(alarmId);
  90. if (mapper.markAlarmRecovered(alarmId) != 1) throw new IllegalStateException("告警恢复更新未成功");
  91. writeLifecycleLog(alarmId, AlarmLogOperTypeEnum.HANDLED,
  92. "监测恢复,报警自动解除", "系统自动", LocalDateTime.now(), beforeLogId);
  93. }
  94. int count = alarmIds.size();
  95. if (count > 0) {
  96. log.info("正常采集数据自动处理告警: deviceCode={}, warningCode={}, count={}", deviceCode, warningCode, count);
  97. }
  98. return count;
  99. });
  100. }
  101. private void writeLifecycleLog(String alarmId, AlarmLogOperTypeEnum type, String description,
  102. String actor, LocalDateTime time, long afterLogId) {
  103. if (alarmId == null || alarmId.trim().isEmpty()) throw new IllegalStateException("报警日志缺少报警ID");
  104. int count = logMapper.insertLifecycleIfAbsent(alarmId, type.getCode(), description, actor, time, afterLogId);
  105. if (count != 1 && !(count == 0 && logMapper.existsLifecycle(alarmId, type.getCode(), afterLogId))) {
  106. throw new IllegalStateException("报警日志写入失败");
  107. }
  108. }
  109. private void lockKey(String deviceCode, String warningCode) {
  110. if (!Integer.valueOf(1).equals(mapper.lockAlarmKey(deviceCode, warningCode))) {
  111. throw new IllegalStateException("未取得告警入库事务锁");
  112. }
  113. }
  114. private void validateKey(String deviceCode, String warningCode) {
  115. if (deviceCode == null || deviceCode.trim().isEmpty() || warningCode == null || warningCode.trim().isEmpty()) {
  116. throw new IllegalArgumentException("告警必须包含device_code和warning_code");
  117. }
  118. }
  119. }