Browse Source

feat:设备告警恢复/设备上线更换新的消息推送模版
feat:新增传感器数据导出接口
fix:修复小程序告警次数统计条数不正确bug
fix:修复pc预警日志页面告警类型下拉框数据

黄渊昊 4 months ago
parent
commit
9d9a7beb52
25 changed files with 232 additions and 120 deletions
  1. 9 6
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/offline/DeviceOfflineDetectionService.java
  2. 1 1
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/service/SensorAlarmService.java
  3. 10 3
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/service/SensorAlarmServiceImpl.java
  4. 23 7
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/service/messagepush/LocalSensorAlarmMessagePushService.java
  5. 2 2
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/service/messagepush/MessagePushService.java
  6. 47 6
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/service/messagepush/RedisSensorAlarmMessagePushService.java
  7. 2 2
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/service/messagepush/impl/DingTalkMessagePushService.java
  8. 2 2
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/service/messagepush/impl/EmailPushService.java
  9. 2 2
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/service/messagepush/impl/PushNotificationService.java
  10. 2 2
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/service/messagepush/impl/SMSPushService.java
  11. 6 2
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/service/messagepush/impl/WechatMessagePushService.java
  12. 2 2
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/service/messagepush/impl/WechatMiniMessagePushService.java
  13. 2 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/config/JfcloudColdChainConstants.java
  14. 12 5
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/service/JfcloudSensorDataService.java
  15. 3 3
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/app/controller/AppController.java
  16. 29 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/app/param/export/AppTrendParam.java
  17. 0 30
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/app/param/export/TrendParam.java
  18. 24 18
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/app/service/AppDeviceService.java
  19. 25 11
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitornotice/controller/MonitorNoticeController.java
  20. 2 6
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitornotice/param/TrendParam.java
  21. 2 3
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitornotice/service/impl/MonitorNoticeServiceImpl.java
  22. 6 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/push/param/AlarmRecoverPushParam.java
  23. 5 0
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/push/param/OnlinePushParam.java
  24. 12 5
      snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/push/utils/PushUtil.java
  25. 2 2
      snowy-web-app/src/main/resources/application.properties

+ 9 - 6
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/offline/DeviceOfflineDetectionService.java

@@ -21,6 +21,7 @@ import vip.xiaonuo.coldchain.modular.monitortarget.service.MonitorTargetService;
 import vip.xiaonuo.coldchain.modular.monitortargetregion.entity.MonitorTargetRegion;
 import vip.xiaonuo.coldchain.modular.monitortargetregion.service.MonitorTargetRegionService;
 import vip.xiaonuo.common.cache.CommonCacheOperator;
+import vip.xiaonuo.common.exception.CommonException;
 
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
@@ -133,14 +134,16 @@ public class DeviceOfflineDetectionService {
                 log.warn("设备 {}-{} 已离线,最后上报时间:{}。", deviceCode, route, lastReportTimeStr);
                 publishAlarm(deviceCode, route, lastReportTimeStr, "设备离线", SensorAlarmType.SENSOR_OFF_LINE.getDeviceCode());
             } else {
-                // TODO
-                // 怎么之前是离线才推送它上线
-                MonitorTargetRegion monitorTargetRegion = monitorTargetRegionService.findOneByDeviceCodeAndSensorNo(deviceCode, route);
-                if (redisSensorAlarmMessagePushService.hasExceededPushLimitLike("*", monitorTargetRegion.getId(), route,"设备离线")) {
-                    publishAlarm(deviceCode, route, lastReportTimeStr, "设备上线",SensorAlarmType.SENSOR_ON_LINE.getDeviceCode());
-                }
                 status = MonitorStatusEnum.ONLINE;
                 log.debug("设备 {}-{} 在线,最后上报时间:{}。", deviceCode, route, lastReportTimeStr);
+                MonitorTargetRegion monitorTargetRegion = monitorTargetRegionService.findOneByDeviceCodeAndSensorNo(deviceCode, route);
+                if (Objects.nonNull(monitorTargetRegion)) {
+                    if (redisSensorAlarmMessagePushService.hasExceededPushLimitLike("*", monitorTargetRegion.getId(), route,"设备离线")) {
+                        publishAlarm(deviceCode, route, lastReportTimeStr, "设备上线",SensorAlarmType.SENSOR_ON_LINE.getDeviceCode());
+                    }
+                } else {
+                    throw new CommonException("该设备不存在");
+                }
             }
         } else {
             status = MonitorStatusEnum.OFF;

+ 1 - 1
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/service/SensorAlarmService.java

@@ -24,5 +24,5 @@ public interface SensorAlarmService extends IService<SensorAlarm> {
 
     long countAlarms(String startDate, String endDate, String sensorCode, String sensorRoute, SensorAlarmType type);
 
-    List<String> getAlarmTypeList();
+    List<String> getAlarmTypeList(String type);
 }

+ 10 - 3
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/service/SensorAlarmServiceImpl.java

@@ -95,12 +95,19 @@ public class SensorAlarmServiceImpl extends ServiceImpl<SensorAlarmMapper, Senso
     }
 
     @Override
-    public List<String> getAlarmTypeList() {
+    public List<String> getAlarmTypeList(String type) {
         SaBaseLoginUser loginUser = StpLoginUserUtil.getLoginUser();
         LambdaQueryWrapper<SensorAlarm> queryWrapper = new LambdaQueryWrapper<>();
         queryWrapper.eq(SensorAlarm::getCreateOrg, loginUser.getOrgId())
-                .eq(SensorAlarm::getDeleteFlag, CommonDeleteFlagEnum.NOT_DELETE)
-                .groupBy(SensorAlarm::getAlarmType);
+                .eq(SensorAlarm::getDeleteFlag, CommonDeleteFlagEnum.NOT_DELETE);
+        if (type.equals("system")) {
+            queryWrapper.and(q -> q.eq(SensorAlarm::getType, SensorAlarmType.SENSOR_OFF_LINE.getDeviceCode())
+                    .or().eq(SensorAlarm::getType, SensorAlarmType.SENSOR_ON_LINE.getDeviceCode()));
+        } else {
+            queryWrapper.and(q -> q.eq(SensorAlarm::getType, SensorAlarmType.DATA_ALARM.getDeviceCode())
+                    .or().eq(SensorAlarm::getType, SensorAlarmType.DATA_RESTORE_ALARM.getDeviceCode()));
+        }
+                queryWrapper.groupBy(SensorAlarm::getAlarmType);
         return list(queryWrapper).stream().map(SensorAlarm::getAlarmType).collect(Collectors.toList());
     }
 

+ 23 - 7
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/service/messagepush/LocalSensorAlarmMessagePushService.java

@@ -42,7 +42,7 @@ public class LocalSensorAlarmMessagePushService {
 
         for (String openid : openids) {
             // 判断该用户对该设备和告警类型的推送是否超过限制
-            if (hasExceededPushLimit(openid, deviceID,alarm.getSensorRoute(), alarmType)) {
+            if (hasExceededPushLimit(openid, deviceID, alarm.getSensorRoute(), alarmType)) {
                 log.info("用户 {} 对设备 {} 的告警类型 {} \n在过去{}分钟内推送次数超过限制,跳过推送", openid, deviceID, alarmType, JfcloudColdChainConstants.MESS_PUSH_TIME_WINDOW / 60 / 1000);
                 continue;
             }
@@ -52,19 +52,27 @@ public class LocalSensorAlarmMessagePushService {
                 if (pushService != null) {
                     try {
                         boolean b = false;
+                        Long firstAlarmTime;
                         switch (alarm.getType()) {
                             case "0":
                                 b = pushService.sendAlarmMessage(alarm);
                                 break;
                             case "1":
-                                b = pushService.sendAlarmRecoverMessage(alarm);
+                                b = pushService.sendOfflineMessage(alarm);
                                 break;
                             case "2":
-                                b = pushService.sendOfflineMessage(alarm);
+                                firstAlarmTime = (Long) getAlarmTime(deviceID, alarm.getSensorRoute(), alarmType);
+                                b = pushService.sendAlarmRecoverMessage(alarm, firstAlarmTime);
+                                if (b) {
+                                    delAlarmTime(deviceID, alarm.getSensorRoute(), alarmType);
+                                }
                                 break;
                             case "4":
-                                b = pushService.sendOnlineMessage(alarm);
-                                break;
+                                firstAlarmTime = (Long) getAlarmTime(deviceID, alarm.getSensorRoute(), alarmType);
+                                b = pushService.sendOnlineMessage(alarm, firstAlarmTime);
+                                if (b) {
+                                    delAlarmTime(deviceID, alarm.getSensorRoute(), alarmType);
+                                }
 
                         }
                         // 设置已通知状态
@@ -77,12 +85,20 @@ public class LocalSensorAlarmMessagePushService {
                 }
             }
             // 记录推送时间
-            recordPushTime(openid, deviceID, alarm.getSensorRoute() , alarmType);
+            recordPushTime(openid, deviceID, alarm.getSensorRoute(), alarmType);
         }
         // 将告警信息存储到数据库
         saveAlarmToDatabase(alarm);
     }
 
+    private void delAlarmTime(String deviceID, Integer sensorRoute, String alarmType) {
+
+    }
+
+    private Object getAlarmTime(String deviceID, Integer sensorRoute, String alarmType) {
+        return null;
+    }
+
     /**
      * 判断用户对某设备和某告警类型的推送是否超过了推送次数限制
      *
@@ -113,7 +129,7 @@ public class LocalSensorAlarmMessagePushService {
      * @return 唯一的键值
      */
     private String generatePushHistoryKey(String openid, String deviceID, Integer sensorRoute, String alarmType) {
-        return openid + "_" + deviceID + "_" + + sensorRoute +  "_" + alarmType;
+        return openid + "_" + deviceID + "_" + +sensorRoute + "_" + alarmType;
     }
 
     /**

+ 2 - 2
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/service/messagepush/MessagePushService.java

@@ -27,9 +27,9 @@ public interface MessagePushService {
      */
     boolean sendAlarmMessage(SensorAlarm alarm);
 
-    boolean sendAlarmRecoverMessage(SensorAlarm alarm);
+    boolean sendAlarmRecoverMessage(SensorAlarm alarm, Long firstAlarmTime);
 
     boolean sendOfflineMessage(SensorAlarm alarm);
 
-    boolean sendOnlineMessage(SensorAlarm alarm);
+    boolean sendOnlineMessage(SensorAlarm alarm, Long firstAlarmTime);
 }

+ 47 - 6
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/service/messagepush/RedisSensorAlarmMessagePushService.java

@@ -36,6 +36,10 @@ public class RedisSensorAlarmMessagePushService {
     public void pushAlarmMessage(SensorAlarm alarm) {
         String deviceID = alarm.getDeviceId();
         String alarmType = alarm.getAlarmType();
+        if (alarm.getType().equals("0") || alarm.getType().equals("1")) {
+//        将第一次预警时间存入redis
+            recordAlarmTime(deviceID, alarm.getSensorRoute(), alarm.getType());
+        }
         Map<String, SensorAlarmUser> users = Optional.ofNullable(alarm.getAlarmUsers()).orElse(Collections.emptyList()) // 如果为 null 则返回空集合
                 .stream().filter(Objects::nonNull) // 过滤掉 null 的用户
                 .filter(user -> StrUtil.isNotBlank(user.getOpenId())) // 过滤掉 openId 为空的用户
@@ -49,17 +53,19 @@ public class RedisSensorAlarmMessagePushService {
         List<SensorAlarmUser> alarmUsers = users.values().stream().toList();
         for (String openid : users.keySet()) {
             SensorAlarmUser user = users.get(openid);
-            if (hasExceededPushLimit(openid, deviceID,alarm.getSensorRoute(), alarmType)) {
+            if (hasExceededPushLimit(openid, deviceID, alarm.getSensorRoute(), alarmType)) {
                 log.info("用户 {} 对设备 {} 的告警类型 {} \n在过去{}分钟内推送次数超过限制,跳过推送", openid, deviceID, alarmType, JfcloudColdChainConstants.MESS_PUSH_TIME_WINDOW / 60 / 1000);
                 continue;
             }
             // 只对当前人发送预警消息
             alarm.setAlarmUsers(List.of(user));
+            recordPushTime(openid, deviceID, alarm.getSensorRoute(), alarmType);
             for (NotificationChannel channel : alarm.getNotificationChannel()) {
                 MessagePushService pushService = pushServices.get(channel.name());
                 if (pushService != null) {
                     try {
                         boolean b = false;
+                        Long firstAlarmTime;
                         switch (alarm.getType()) {
                             case "0":
                                 b = pushService.sendAlarmMessage(alarm);
@@ -68,10 +74,18 @@ public class RedisSensorAlarmMessagePushService {
                                 b = pushService.sendOfflineMessage(alarm);
                                 break;
                             case "2":
-                                b = pushService.sendAlarmRecoverMessage(alarm);
+                                firstAlarmTime = (Long) getAlarmTime(deviceID, alarm.getSensorRoute(), "0");
+                                b = pushService.sendAlarmRecoverMessage(alarm, firstAlarmTime);
+                                if (b) {
+                                    delAlarmTime(deviceID, alarm.getSensorRoute(), "0");
+                                }
                                 break;
                             case "4":
-                                b = pushService.sendOnlineMessage(alarm);
+                                firstAlarmTime = (Long) getAlarmTime(deviceID, alarm.getSensorRoute(), "1");
+                                b = pushService.sendOnlineMessage(alarm, firstAlarmTime);
+                                if (b) {
+                                    delAlarmTime(deviceID, alarm.getSensorRoute(), "1");
+                                }
                                 break;
                         }
                         alarm.setNotified(b);
@@ -82,14 +96,35 @@ public class RedisSensorAlarmMessagePushService {
                     log.warn("不支持的通知渠道: {}", channel.getChannelName());
                 }
             }
-            recordPushTime(openid, deviceID,alarm.getSensorRoute(), alarmType);
         }
         // 还原这条消息的发送所有人
         alarm.setAlarmUsers(alarmUsers);
         saveAlarmToDatabase(alarm);
     }
 
-    public boolean hasExceededPushLimit(String openid, String deviceID, Integer sensorRoute,  String alarmType) {
+    //    获取设备第一次报警时间
+    private Object getAlarmTime(String deviceID, Integer sensorRoute, String alarmType) {
+        String key = generateAlarmHistoryKey(deviceID, sensorRoute, alarmType);
+        return redisTemplate.opsForList().index(key, 0);
+    }
+
+    //    删除设备第一次报警时间
+    private void delAlarmTime(String deviceID, Integer sensorRoute, String alarmType) {
+        String key = generateAlarmHistoryKey(deviceID, sensorRoute, alarmType);
+        redisTemplate.delete(key);
+    }
+
+    //    存储设备第一次报警时间
+    public void recordAlarmTime(String deviceID, Integer sensorRoute, String alarmType) {
+        String alarmKey = generateAlarmHistoryKey(deviceID, sensorRoute, alarmType);
+        List<Object> pushTimes = redisTemplate.opsForList().range(alarmKey, 0, -1);
+        //    判断是否有报警数据
+        if (pushTimes.isEmpty()) {
+            redisTemplate.opsForList().rightPush(alarmKey, System.currentTimeMillis());
+        }
+    }
+
+    public boolean hasExceededPushLimit(String openid, String deviceID, Integer sensorRoute, String alarmType) {
         String key = generatePushHistoryKey(openid, deviceID, sensorRoute, alarmType);
         List<Object> pushTimes = redisTemplate.opsForList().range(key, 0, -1);
         if (pushTimes == null || pushTimes.isEmpty()) {
@@ -100,7 +135,7 @@ public class RedisSensorAlarmMessagePushService {
         return pushTimes.size() >= JfcloudColdChainConstants.MESS_PUSH_MAX_PUSH_COUNT;
     }
 
-    public boolean hasExceededPushLimitLike(String openid, String deviceID, Integer sensorRoute,  String alarmType) {
+    public boolean hasExceededPushLimitLike(String openid, String deviceID, Integer sensorRoute, String alarmType) {
         String key = generatePushHistoryKey(openid, deviceID, sensorRoute, alarmType);
         Set<String> keys = redisTemplate.keys(key);
         if (keys == null || keys.isEmpty()) {
@@ -119,6 +154,7 @@ public class RedisSensorAlarmMessagePushService {
     }
 
     private void recordPushTime(String openid, String deviceID, Integer sensorRoute, String alarmType) {
+        log.info("数据存入redis数据库……");
         String key = generatePushHistoryKey(openid, deviceID, sensorRoute, alarmType);
         long currentTime = System.currentTimeMillis();
         redisTemplate.opsForList().rightPush(key, currentTime);
@@ -137,4 +173,9 @@ public class RedisSensorAlarmMessagePushService {
         return JfcloudColdChainConstants.REDIS_PUSH_HISTORY_KEY_PREFIX
                 + ":" + openid + ":" + deviceID + ":" + sensorRoute + ":" + alarmType;
     }
+
+    private String generateAlarmHistoryKey(String deviceID, Integer sensorRoute, String alarmType) {
+        return JfcloudColdChainConstants.REDIS_ALARM_HISTORY_KEY_PREFIX
+                + ":" + deviceID + ":" + sensorRoute + ":" + alarmType;
+    }
 }

+ 2 - 2
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/service/messagepush/impl/DingTalkMessagePushService.java

@@ -21,7 +21,7 @@ public class DingTalkMessagePushService implements MessagePushService {
     }
 
     @Override
-    public boolean sendAlarmRecoverMessage(SensorAlarm alarm) {
+    public boolean sendAlarmRecoverMessage(SensorAlarm alarm, Long firstAlarmTime) {
         return false;
     }
 
@@ -31,7 +31,7 @@ public class DingTalkMessagePushService implements MessagePushService {
     }
 
     @Override
-    public boolean sendOnlineMessage(SensorAlarm alarm) {
+    public boolean sendOnlineMessage(SensorAlarm alarm, Long firstAlarmTime) {
         return false;
     }
 }

+ 2 - 2
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/service/messagepush/impl/EmailPushService.java

@@ -23,7 +23,7 @@ public class EmailPushService implements MessagePushService {
     }
 
     @Override
-    public boolean sendAlarmRecoverMessage(SensorAlarm alarm) {
+    public boolean sendAlarmRecoverMessage(SensorAlarm alarm, Long firstAlarmTime) {
         return false;
     }
 
@@ -33,7 +33,7 @@ public class EmailPushService implements MessagePushService {
     }
 
     @Override
-    public boolean sendOnlineMessage(SensorAlarm alarm) {
+    public boolean sendOnlineMessage(SensorAlarm alarm, Long firstAlarmTime) {
         return false;
     }
 

+ 2 - 2
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/service/messagepush/impl/PushNotificationService.java

@@ -23,7 +23,7 @@ public class PushNotificationService implements MessagePushService {
     }
 
     @Override
-    public boolean sendAlarmRecoverMessage(SensorAlarm alarm) {
+    public boolean sendAlarmRecoverMessage(SensorAlarm alarm, Long firstAlarmTime) {
         return false;
     }
 
@@ -33,7 +33,7 @@ public class PushNotificationService implements MessagePushService {
     }
 
     @Override
-    public boolean sendOnlineMessage(SensorAlarm alarm) {
+    public boolean sendOnlineMessage(SensorAlarm alarm, Long firstAlarmTime) {
         return false;
     }
 

+ 2 - 2
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/service/messagepush/impl/SMSPushService.java

@@ -23,7 +23,7 @@ public class SMSPushService implements MessagePushService {
     }
 
     @Override
-    public boolean sendAlarmRecoverMessage(SensorAlarm alarm) {
+    public boolean sendAlarmRecoverMessage(SensorAlarm alarm, Long firstAlarmTime) {
         return false;
     }
 
@@ -33,7 +33,7 @@ public class SMSPushService implements MessagePushService {
     }
 
     @Override
-    public boolean sendOnlineMessage(SensorAlarm alarm) {
+    public boolean sendOnlineMessage(SensorAlarm alarm, Long firstAlarmTime) {
         return false;
     }
 

+ 6 - 2
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/service/messagepush/impl/WechatMessagePushService.java

@@ -54,12 +54,14 @@ public class WechatMessagePushService implements MessagePushService {
     }
 
     @Override
-    public boolean sendAlarmRecoverMessage(SensorAlarm alarm) {
+    public boolean sendAlarmRecoverMessage(SensorAlarm alarm, Long firstAlarmTime) {
         AlarmRecoverPushParam pushParam = new AlarmRecoverPushParam();
         pushParam.setDeviceName(alarm.getSource());
         pushParam.setRecoverTime(new Date());
         pushParam.setType(alarm.getAlarmType());
         pushParam.setValue(alarm.getValue() + "/" + alarm.getThreshold());
+        Date date = new Date(firstAlarmTime);
+        pushParam.setFirstAlarmTime(date);
         pushParam.setUserIdList(alarm.getAlarmUsers().stream().map(SensorAlarmUser::getOpenId).collect(Collectors.toSet()));
         if (alarm.getMessage().length() > 17) {
             pushParam.setRecoverCause(alarm.getMessage().substring(0, 17) + "...");
@@ -93,11 +95,13 @@ public class WechatMessagePushService implements MessagePushService {
     }
 
     @Override
-    public boolean sendOnlineMessage(SensorAlarm alarm) {
+    public boolean sendOnlineMessage(SensorAlarm alarm, Long firstAlarmTime) {
         OnlinePushParam pushParam = new OnlinePushParam();
         pushParam.setOnlineTime(new Date());
         pushParam.setDeviceName(alarm.getSource());
         pushParam.setStatus("上线");
+        Date date = new Date(firstAlarmTime);
+        pushParam.setFirstAlarmTime(date);
         pushParam.setUserIdList(alarm.getAlarmUsers().stream().map(SensorAlarmUser::getOpenId).collect(Collectors.toSet()));
         if (StrUtil.isNotBlank(alarm.getRoomName())) {
             pushParam.setLocation(alarm.getRoomName());

+ 2 - 2
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/alarm/service/messagepush/impl/WechatMiniMessagePushService.java

@@ -19,7 +19,7 @@ public class WechatMiniMessagePushService implements MessagePushService {
     }
 
     @Override
-    public boolean sendAlarmRecoverMessage(SensorAlarm alarm) {
+    public boolean sendAlarmRecoverMessage(SensorAlarm alarm, Long firstAlarmTime) {
         return false;
     }
 
@@ -29,7 +29,7 @@ public class WechatMiniMessagePushService implements MessagePushService {
     }
 
     @Override
-    public boolean sendOnlineMessage(SensorAlarm alarm) {
+    public boolean sendOnlineMessage(SensorAlarm alarm, Long firstAlarmTime) {
         return false;
     }
 }

+ 2 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/config/JfcloudColdChainConstants.java

@@ -73,6 +73,8 @@ public interface JfcloudColdChainConstants {
      */
     String REDIS_PUSH_HISTORY_KEY_PREFIX = "ALARM_PUSH_HISTORY";
 
+    String REDIS_ALARM_HISTORY_KEY_PREFIX = "ALARM_HISTORY";
+
     /**
      * 每 5 分钟定期执行设备缓存更新
      */

+ 12 - 5
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/core/service/JfcloudSensorDataService.java

@@ -283,19 +283,26 @@ public class JfcloudSensorDataService extends JfcloudFluxDataService<SensorData>
     }
 
     public long getLastMonthAlarmCount(String sensorCode, String sensorRoute) {
-        return getAlarmCount(sensorCode, sensorRoute, SensorAlarmType.DATA_ALARM, true);
+        long l = getAlarmCount(sensorCode, sensorRoute, SensorAlarmType.DATA_ALARM, true)
+                + getAlarmCount(sensorCode, sensorRoute, SensorAlarmType.DATA_RESTORE_ALARM, true);
+        return l;
     }
 
     public long getCurrentMonthAlarmCount(String sensorCode, String sensorRoute) {
-        return getAlarmCount(sensorCode, sensorRoute, SensorAlarmType.DATA_ALARM, false);
+        long l = getAlarmCount(sensorCode, sensorRoute, SensorAlarmType.DATA_ALARM, false)
+                + getAlarmCount(sensorCode, sensorRoute, SensorAlarmType.DATA_RESTORE_ALARM, false);
+        return l;
     }
 
     public long getCurrentMonthOfflineCount(String sensorCode, String sensorRoute) {
-        return getAlarmCount(sensorCode, sensorRoute, SensorAlarmType.SENSOR_OFF_LINE, false);
-    }
+        long l = getAlarmCount(sensorCode, sensorRoute, SensorAlarmType.SENSOR_OFF_LINE, false)
+                + getAlarmCount(sensorCode, sensorRoute, SensorAlarmType.SENSOR_ON_LINE, false);
+        return l;    }
 
     public long getLastMonthOfflineCount(String sensorCode, String sensorRoute) {
-        return getAlarmCount(sensorCode, sensorRoute, SensorAlarmType.SENSOR_OFF_LINE, true);
+        long l = getAlarmCount(sensorCode, sensorRoute, SensorAlarmType.SENSOR_OFF_LINE, true)
+                + getAlarmCount(sensorCode, sensorRoute, SensorAlarmType.SENSOR_ON_LINE, true);
+        return l;
     }
 
 }

+ 3 - 3
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/app/controller/AppController.java

@@ -12,7 +12,7 @@ import org.springframework.http.ResponseEntity;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 import vip.xiaonuo.coldchain.modular.app.param.*;
-import vip.xiaonuo.coldchain.modular.app.param.export.TrendParam;
+import vip.xiaonuo.coldchain.modular.app.param.export.AppTrendParam;
 import vip.xiaonuo.coldchain.modular.app.service.AppDeviceService;
 import vip.xiaonuo.coldchain.modular.app.service.MessageService;
 import vip.xiaonuo.common.pojo.CommonResult;
@@ -133,9 +133,9 @@ public class AppController {
     /**
      * 导出传感器数据
      */
-    @Operation(description = "导出传感器数据")
+    @Operation(summary = "导出传感器数据")
     @GetMapping("/device/export")
-    public CommonResult<String> export(HttpServletResponse response, TrendParam trendParam) {
+    public CommonResult<String> export(HttpServletResponse response, AppTrendParam trendParam) {
         appDeviceService.export(response,trendParam);
         return CommonResult.ok();
     }

+ 29 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/app/param/export/AppTrendParam.java

@@ -0,0 +1,29 @@
+package vip.xiaonuo.coldchain.modular.app.param.export;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class AppTrendParam {
+
+    @Schema(description = "设备编号")
+    @NotBlank(message = "设备编号不能为空")
+    private String sensorCode;
+
+    @Schema(description = "传感器路数")
+    @NotBlank(message = "传感器路数不能为空")
+    private Integer roads;
+
+    @Schema(description = "开始查询时间")
+    @NotBlank(message = "开始时间不能为空")
+    private String startTime;
+
+    @Schema(description = "结束查询时间")
+    @NotBlank(message = "结束时间不能为空")
+    private String endTime;
+
+    private String aggregationWindow;
+}

+ 0 - 30
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/app/param/export/TrendParam.java

@@ -1,30 +0,0 @@
-package vip.xiaonuo.coldchain.modular.app.param.export;
-
-import com.fasterxml.jackson.annotation.JsonFormat;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Getter;
-import lombok.Setter;
-import org.springframework.format.annotation.DateTimeFormat;
-
-import java.util.Date;
-
-@Getter
-@Setter
-public class TrendParam {
-
-    @Schema(description = "设备编号")
-    private String sensorCode;
-
-    @Schema(description = "传感器路数")
-    private Integer roads;
-
-    @DateTimeFormat(pattern = "yyyy-MM-dd")
-    @JsonFormat(pattern = "yyyy-MM-dd")
-    private Date startTime;
-
-    @DateTimeFormat(pattern = "yyyy-MM-dd")
-    @JsonFormat(pattern = "yyyy-MM-dd")
-    private Date endTime;
-
-    private String aggregationWindow;
-}

+ 24 - 18
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/app/service/AppDeviceService.java

@@ -26,9 +26,7 @@ import vip.xiaonuo.coldchain.core.bean.influxdb.SensorData;
 import vip.xiaonuo.coldchain.core.service.JfcloudSensorDataService;
 import vip.xiaonuo.coldchain.modular.app.param.*;
 import vip.xiaonuo.coldchain.modular.app.param.export.ExportParam;
-import vip.xiaonuo.coldchain.modular.app.param.export.TrendParam;
-import vip.xiaonuo.coldchain.modular.monitordevice.entity.MonitorDeviceTemplate;
-import vip.xiaonuo.coldchain.modular.monitordevice.handler.CustomCellWriteHandler;
+import vip.xiaonuo.coldchain.modular.app.param.export.AppTrendParam;
 import vip.xiaonuo.coldchain.modular.monitordevice.service.MonitorDeviceService;
 import vip.xiaonuo.coldchain.modular.monitortarget.entity.MonitorTarget;
 import vip.xiaonuo.coldchain.modular.monitortarget.param.MonitorTargetPageParam;
@@ -37,6 +35,7 @@ import vip.xiaonuo.coldchain.modular.monitortarget.service.MonitorTargetService;
 import vip.xiaonuo.coldchain.modular.monitortargetregion.entity.MonitorTargetRegion;
 import vip.xiaonuo.coldchain.modular.monitortargetregion.service.MonitorTargetRegionService;
 import vip.xiaonuo.common.enums.CommonDeleteFlagEnum;
+import vip.xiaonuo.common.exception.CommonException;
 
 import java.io.IOException;
 import java.io.OutputStream;
@@ -442,48 +441,55 @@ public class AppDeviceService {
         return monitorTargetRegionService.updateById(monitorTargetRegion);
     }
 
-    public void export(HttpServletResponse response, TrendParam trendParam) {
+    public void export(HttpServletResponse response, AppTrendParam trendParam) {
         AppDeviceQueryParams appDeviceQueryParams = BeanUtil.copyProperties(trendParam, AppDeviceQueryParams.class);
+        appDeviceQueryParams.setSensorRoute(trendParam.getRoads());
         SensorEchartDataResult sensorEchartDataResult = queryDataByDeviceIdAndRoads(appDeviceQueryParams);
         MonitorTargetRegion monitorTargetRegion = monitorTargetRegionService.findOneByDeviceCodeAndSensorNo(trendParam.getSensorCode(), trendParam.getRoads());
+        if (Objects.isNull(monitorTargetRegion)) {
+            throw new CommonException("未找到设备编号 [{}] 和传感器编号 [{}] 对应的监控目标区域。", trendParam.getSensorCode(), trendParam.getRoads());
+        }
         List<ExportParam> exportParamList = new ArrayList<>();
         ExportParam exportParam = new ExportParam();
         exportParam.setRegionName(monitorTargetRegion.getName());
         exportParam.setDeviceCode(trendParam.getSensorCode());
         exportParam.setSensorRoad(trendParam.getRoads());
-        List<String> list = List.of(Arrays.toString(monitorTargetRegion.getSensorType().toCharArray()));
-        if (list.contains("W")) {
+        String sensorType = monitorTargetRegion.getSensorType().toUpperCase();
+        if (sensorType.contains("W")) {
             exportParam.setDataType("温度");
             LinkedHashSet<String> x = sensorEchartDataResult.getTemperature().getX();
             List<Float> y = sensorEchartDataResult.getTemperature().getY();
             int i = 0;
             for (String s : x) {
-                exportParam.setTime(s);
-                exportParam.setData(y.get(i));
+                ExportParam exportParamOut = BeanUtil.copyProperties(exportParam, ExportParam.class);
+                exportParamOut.setTime(s);
+                exportParamOut.setData(y.get(i));
                 i++;
-                exportParamList.add(exportParam);
+                exportParamList.add(exportParamOut);
             }
-        } else if (list.contains("S")) {
+        } else if (sensorType.contains("S")) {
             exportParam.setDataType("湿度");
             LinkedHashSet<String> x = sensorEchartDataResult.getHumidity().getX();
             List<Float> y = sensorEchartDataResult.getHumidity().getY();
             int i = 0;
             for (String s : x) {
-                exportParam.setTime(s);
-                exportParam.setData(y.get(i));
+                ExportParam exportParamOut = BeanUtil.copyProperties(exportParam, ExportParam.class);
+                exportParamOut.setTime(s);
+                exportParamOut.setData(y.get(i));
                 i++;
-                exportParamList.add(exportParam);
+                exportParamList.add(exportParamOut);
             }
-        } else if (list.contains("C")) {
+        } else if (sensorType.contains("C")) {
             exportParam.setDataType("二氧化碳浓度");
             LinkedHashSet<String> x = sensorEchartDataResult.getCo2().getX();
             List<Float> y = sensorEchartDataResult.getCo2().getY();
             int i = 0;
             for (String s : x) {
-                exportParam.setTime(s);
-                exportParam.setData(y.get(i));
+                ExportParam exportParamOut = BeanUtil.copyProperties(exportParam, ExportParam.class);
+                exportParamOut.setTime(s);
+                exportParamOut.setData(y.get(i));
                 i++;
-                exportParamList.add(exportParam);
+                exportParamList.add(exportParamOut);
             }
         }
         String fileName = "传感器数据";
@@ -493,7 +499,7 @@ public class AppDeviceService {
 
         response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + URLEncoder.encode(fileName, StandardCharsets.UTF_8) + ".xls");
         try (OutputStream outputStream = response.getOutputStream()) {
-            EasyExcel.write(outputStream, MonitorDeviceTemplate.class)
+            EasyExcel.write(outputStream, ExportParam.class)
                     .excelType(ExcelTypeEnum.XLS)
                     .sheet(sheetName)
                     .doWrite(exportParamList);

+ 25 - 11
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitornotice/controller/MonitorNoticeController.java

@@ -17,32 +17,30 @@ import cn.hutool.core.bean.BeanUtil;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import io.swagger.v3.oas.annotations.Operation;
+import jakarta.servlet.http.HttpServletResponse;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RestController;
 import vip.xiaonuo.coldchain.core.alarm.bean.SensorAlarm;
 import vip.xiaonuo.coldchain.core.alarm.service.SensorAlarmService;
-import vip.xiaonuo.coldchain.core.bean.influxdb.SensorData;
-import vip.xiaonuo.coldchain.modular.app.param.MessagePageParam;
+import vip.xiaonuo.coldchain.modular.app.param.export.AppTrendParam;
+import vip.xiaonuo.coldchain.modular.app.service.AppDeviceService;
+import vip.xiaonuo.coldchain.modular.monitordevice.entity.MonitorDevice;
 import vip.xiaonuo.coldchain.modular.monitordevice.service.MonitorDeviceService;
 import vip.xiaonuo.coldchain.modular.monitornotice.entity.*;
 import vip.xiaonuo.coldchain.modular.monitornotice.param.*;
-import vip.xiaonuo.common.annotation.CommonLog;
 import vip.xiaonuo.common.pojo.CommonResult;
 import vip.xiaonuo.coldchain.modular.monitornotice.service.MonitorNoticeService;
 
 import jakarta.annotation.Resource;
-import jakarta.validation.Valid;
-import jakarta.validation.constraints.NotEmpty;
+
 import java.util.List;
 
 /**
  * 监控通知控制器
  *
  * @author 黄渊昊
- * @date  2024/11/25 10:58
+ * @date 2024/11/25 10:58
  */
 @Tag(name = "监控通知控制器")
 @RestController
@@ -55,12 +53,14 @@ public class MonitorNoticeController {
     private SensorAlarmService sensorAlarmService;
     @Resource
     private MonitorDeviceService monitorDeviceService;
+    @Resource
+    private AppDeviceService appDeviceService;
 
     /**
      * 获取监控通知分页
      *
      * @author 黄渊昊
-     * @date  2024/11/25 10:58
+     * @date 2024/11/25 10:58
      */
     @Operation(summary = "获取监控通知分页")
     @SaCheckPermission("/coldchain/monitornotice/page")
@@ -75,8 +75,8 @@ public class MonitorNoticeController {
     @Operation(summary = "获取告警类型")
     @SaCheckPermission("/coldchain/monitornotice/getAlarmTypeList")
     @GetMapping("/coldchain/monitornotice/getAlarmTypeList")
-    public CommonResult<List<String>> getAlarmTypeList() {
-        return CommonResult.data(sensorAlarmService.getAlarmTypeList());
+    public CommonResult<List<String>> getAlarmTypeList(String type) {
+        return CommonResult.data(sensorAlarmService.getAlarmTypeList(type));
     }
 
     /**
@@ -120,4 +120,18 @@ public class MonitorNoticeController {
     public CommonResult<Realtime> getRealtime(RealtimeParam realtimeParam) {
         return CommonResult.data(monitorNoticeService.getRealtime(realtimeParam));
     }
+
+
+    /**
+     * 导出传感器数据
+     */
+    @Operation(summary = "导出传感器数据")
+    @GetMapping("/coldchain/monitornotice/export")
+    public CommonResult<String> export(HttpServletResponse response, TrendParam trendParam) {
+        MonitorDevice monitorDevice = monitorDeviceService.queryEntity(trendParam.getDeviceId());
+        AppTrendParam appTrendParam = BeanUtil.copyProperties(trendParam, AppTrendParam.class);
+        appTrendParam.setSensorCode(monitorDevice.getDeviceCode());
+        appDeviceService.export(response, appTrendParam);
+        return CommonResult.ok();
+    }
 }

+ 2 - 6
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitornotice/param/TrendParam.java

@@ -15,13 +15,9 @@ public class TrendParam {
 
     private Integer roads;
 
-    @DateTimeFormat(pattern = "yyyy-MM-dd")
-    @JsonFormat(pattern = "yyyy-MM-dd")
-    private Date startTime;
+    private String startTime;
 
-    @DateTimeFormat(pattern = "yyyy-MM-dd")
-    @JsonFormat(pattern = "yyyy-MM-dd")
-    private Date endTime;
+    private String endTime;
 
     private String aggregationWindow;
 }

+ 2 - 3
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/monitornotice/service/impl/MonitorNoticeServiceImpl.java

@@ -142,11 +142,10 @@ public class MonitorNoticeServiceImpl extends ServiceImpl<MonitorNoticeMapper, M
     public Trend getTrend(TrendParam trendParam) {
         MonitorDevice monitorDevice = monitorDeviceService.getById(trendParam.getDeviceId());
         MonitorTargetRegion oneByDeviceCodeAndSensorNo = monitorTargetRegionService.findOneByDeviceCodeAndSensorNo(monitorDevice.getDeviceCode(), trendParam.getRoads());
-        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
         AppDeviceQueryParams appDeviceQueryParams = new AppDeviceQueryParams();
         appDeviceQueryParams.setSensorCode(oneByDeviceCodeAndSensorNo.getSensorCode());
-        appDeviceQueryParams.setStartTime(format.format(trendParam.getStartTime()));
-        appDeviceQueryParams.setEndTime(format.format(trendParam.getEndTime()));
+        appDeviceQueryParams.setStartTime(trendParam.getStartTime());
+        appDeviceQueryParams.setEndTime(trendParam.getEndTime());
         appDeviceQueryParams.setSensorRoute(trendParam.getRoads());
         appDeviceQueryParams.setAggregationWindow(trendParam.getAggregationWindow());
         SensorEchartDataResult sensorEchartDataResult = appDeviceService.queryDataByDeviceIdAndRoads(appDeviceQueryParams);

+ 6 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/push/param/AlarmRecoverPushParam.java

@@ -39,6 +39,12 @@ public class AlarmRecoverPushParam {
      */
     @Size(min = 1, max = 20)
     private String recoverCause;
+
+    /**
+     * 离线时间
+     */
+    private Date firstAlarmTime;
+
     /**
      * 用户名
      */

+ 5 - 0
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/push/param/OnlinePushParam.java

@@ -35,6 +35,11 @@ public class OnlinePushParam {
      */
     private Date OnlineTime;
 
+    /**
+     * 离线时间
+     */
+    private Date firstAlarmTime;
+
     /**
      * 用户名
      */

+ 12 - 5
snowy-plugin/snowy-plugin-coldchain/src/main/java/vip/xiaonuo/coldchain/modular/push/utils/PushUtil.java

@@ -144,11 +144,12 @@ public class PushUtil {
                 pushParam.setValue(" ");
             }
             templateMessage.addData(new WxMpTemplateData("character_string47", pushParam.getValue()));
-            //恢复原因
-            if (StrUtil.isBlank(pushParam.getRecoverCause())) {
-                pushParam.setRecoverCause(" ");
+            //报警时间
+            if (Objects.isNull(pushParam.getFirstAlarmTime())) {
+                pushParam.setFirstAlarmTime(new Date());
             }
-            templateMessage.addData(new WxMpTemplateData("thing16", pushParam.getRecoverCause()));
+            String firstAlarmTimeFormat = DateUtil.format(pushParam.getFirstAlarmTime(), "yyyy-MM-dd HH:mm:ss");
+            templateMessage.addData(new WxMpTemplateData("time5", firstAlarmTimeFormat));
             //恢复时间
             if (Objects.isNull(pushParam.getRecoverTime())) {
                 pushParam.setRecoverTime(new Date());
@@ -203,6 +204,12 @@ public class PushUtil {
                 pushParam.setLocation(" ");
             }
             templateMessage.addData(new WxMpTemplateData("thing11", pushParam.getLocation()));
+            //上次离线时间
+            if (Objects.isNull(pushParam.getFirstAlarmTime())) {
+                pushParam.setFirstAlarmTime(new Date());
+            }
+            String firstAlarmTimeFormat = DateUtil.format(pushParam.getFirstAlarmTime(), "yyyy-MM-dd HH:mm:ss");
+            templateMessage.addData(new WxMpTemplateData("time22", firstAlarmTimeFormat));
             //上线时间
             if (Objects.isNull(pushParam.getOnlineTime())) {
                 pushParam.setOnlineTime(new Date());
@@ -220,7 +227,7 @@ public class PushUtil {
     }
 
     /**
-     * 设备线消息推送
+     * 设备线消息推送
      */
     public static String offlinePush(@Valid OfflinePushParam pushParam) {
         WxMpTemplateMessage.MiniProgram miniProgram = getMiniProgram(pushParam.getUserIdList());

+ 2 - 2
snowy-web-app/src/main/resources/application.properties

@@ -247,8 +247,8 @@ wechat.appID=wx49b1fb6f9c0d0b8d
 wechat.secret=6b177348efc2b21da105fc126b933db9
 wechat.templateId=WgKOAJrnNhnr2lIkfI_vppfY--7oImjddrd4GPnE_UA
 wechat.alarmsTemplateId=WgKOAJrnNhnr2lIkfI_vppfY--7oImjddrd4GPnE_UA
-wechat.alarmsRecoverTemplateId=	VTN3rREcSxV9Hv9WKgu_j7OwQuoVALjptMAwlZhfPH0
-wechat.OnlineTemplateId=nQXwP-WWRi71C2-9-EnYfDSuQ2ez1RBS585ra6aeKvM
+wechat.alarmsRecoverTemplateId=	VTN3rREcSxV9Hv9WKgu_j4zRwYZ6Ym-Xuj8GSsrxDS0
+wechat.OnlineTemplateId=nQXwP-WWRi71C2-9-EnYfHZsfGiwR9ciMb3spOX4KMM
 wechat.offlineTemplateId=qxBSnOKZcN6MpC8Vwpy1lyIfB8ZTxyITR3f-Bzv8zGs
 wechat.miniProgram=wx1eebc072a607c055
 wechat.accessTokenUrl=https://api.weixin.qq.com/sns/oauth2/access_token