Bläddra i källkod

fix: 修复设备离线提醒丢失问题

- WxPbUtil.sendPublicTemplateMessage 异常不再被吞掉,调用方可感知发送失败
- doNotify 只在至少一人发送成功后标记已通知,全部失败保留重试机会
- reconcileUnnotifiedFaults 反重key存在时跳过而非静默标记
- 移除 handleOfflineFault/handleOnlineRecovery 的 @Async 消除竞态
- handleRecovery 找不到记录时防御性清理残留反重key

Co-Authored-By: Claude <noreply@anthropic.com>
skyline 1 månad sedan
förälder
incheckning
18093bb5b7

+ 1 - 0
car-wash-common/src/main/java/com/kym/common/utils/wx/WxPbUtil.java

@@ -160,6 +160,7 @@ public class WxPbUtil {
             }
         } catch (Exception e) {
             logger.error("WxPbUtil.sendPublicTemplateMessage error ", e);
+            throw new RuntimeException("发送模板消息失败: " + e.getMessage(), e);
         }
     }
 

+ 21 - 11
car-wash-service/src/main/java/com/kym/service/impl/FaultNotificationServiceImpl.java

@@ -14,7 +14,6 @@ import com.kym.service.MpMsgTemplateService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.data.redis.core.StringRedisTemplate;
-import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
 import java.time.Duration;
@@ -53,26 +52,22 @@ public class FaultNotificationServiceImpl implements FaultNotificationService {
     }
 
     @Override
-    @Async
     public void handleOfflineFault(String stationId, String deviceName) {
         handleFault(stationId, deviceName, FaultType.OFFLINE, null);
     }
 
     @Override
-    @Async
     public void handleOnlineRecovery(String stationId, String deviceName) {
         handleRecovery(stationId, deviceName, FaultType.OFFLINE);
     }
 
     @Override
-    @Async
     public void handleResourceShortage(String stationId, String deviceName, String faultType) {
         FaultType type = FaultType.fromCode(faultType);
         handleFault(stationId, deviceName, type, null);
     }
 
     @Override
-    @Async
     public void handleResourceRecovery(String stationId, String deviceName, String faultType) {
         FaultType type = FaultType.fromCode(faultType);
         handleRecovery(stationId, deviceName, type);
@@ -99,7 +94,7 @@ public class FaultNotificationServiceImpl implements FaultNotificationService {
 
                 var redisKey = buildAntiDupKey(record.getFaultType(), record.getDeviceName(), record.getStationId());
                 if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(redisKey))) {
-                    faultRecordService.markNotified(record.getId());
+                    log.debug("反重key存在,跳过等待下次兜底: recordId={}", record.getId());
                 } else {
                     doNotify(record);
                 }
@@ -135,7 +130,9 @@ public class FaultNotificationServiceImpl implements FaultNotificationService {
     private void handleRecovery(String stationId, String deviceName, FaultType faultType) {
         var record = faultRecordService.getUnrecovered(stationId, deviceName, faultType.getCode());
         if (record == null) {
-            log.debug("未找到未恢复的故障记录: stationId={}, deviceName={}, faultType={}", stationId, deviceName, faultType.getCode());
+            log.debug("未找到未恢复的故障记录,清理可能残留的反重key: stationId={}, deviceName={}, faultType={}",
+                    stationId, deviceName, faultType.getCode());
+            stringRedisTemplate.delete(buildAntiDupKey(faultType.getCode(), deviceName, stationId));
             return;
         }
 
@@ -166,18 +163,25 @@ public class FaultNotificationServiceImpl implements FaultNotificationService {
         }
 
         var params = buildFaultAlertParams(record);
+        int successCount = 0;
         for (var openid : subscribers) {
             try {
                 WxPbUtil.sendPublicTemplateMessage(openid, template.getTemplateId(), params, "", "");
+                successCount++;
             } catch (Exception e) {
                 log.error("发送故障提醒失败: openid={}, recordId={}", openid, record.getId(), e);
             }
         }
 
-        faultRecordService.markNotified(record.getId());
-        stringRedisTemplate.opsForValue().set(
-                buildAntiDupKey(record.getFaultType(), record.getDeviceName(), record.getStationId()),
-                "1", ANTI_DUP_TTL);
+        if (successCount > 0) {
+            faultRecordService.markNotified(record.getId());
+            stringRedisTemplate.opsForValue().set(
+                    buildAntiDupKey(record.getFaultType(), record.getDeviceName(), record.getStationId()),
+                    "1", ANTI_DUP_TTL);
+        } else {
+            log.warn("故障提醒全部发送失败({}/{}), 保留未通知状态等待下次兜底: recordId={}",
+                    successCount, subscribers.size(), record.getId());
+        }
     }
 
     private void sendRecoveryNotification(FaultRecord record) {
@@ -193,13 +197,19 @@ public class FaultNotificationServiceImpl implements FaultNotificationService {
         }
 
         var params = buildFaultRecoverParams(record);
+        int successCount = 0;
         for (var openid : subscribers) {
             try {
                 WxPbUtil.sendPublicTemplateMessage(openid, template.getTemplateId(), params, "", "");
+                successCount++;
             } catch (Exception e) {
                 log.error("发送故障恢复提醒失败: openid={}, recordId={}", openid, record.getId(), e);
             }
         }
+        if (successCount == 0) {
+            log.warn("故障恢复提醒全部发送失败({}/{}): recordId={}",
+                    successCount, subscribers.size(), record.getId());
+        }
     }
 
     private Map<String, String> buildFaultAlertParams(FaultRecord record) {