Просмотр исходного кода

feat(anomaly-notification): 实现系统异常消息发送功能,支持向患者及其绑定的家属和医生推送警告信息

mcbaiyun 2 недель назад
Родитель
Сommit
15e2e90153

+ 97 - 0
src/main/java/work/baiyun/chronicdiseaseapp/aspect/HealthDataCriticalAlertAspect.java

@@ -12,6 +12,12 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.LoggerFactory;
 import work.baiyun.chronicdiseaseapp.mapper.CriticalValueMapper;
 import work.baiyun.chronicdiseaseapp.mapper.CriticalValueMapper;
 import work.baiyun.chronicdiseaseapp.model.po.CriticalValue;
 import work.baiyun.chronicdiseaseapp.model.po.CriticalValue;
+import work.baiyun.chronicdiseaseapp.model.vo.SystemAnomalySendRequest;
+import work.baiyun.chronicdiseaseapp.enums.MessageType;
+import work.baiyun.chronicdiseaseapp.service.MessageService;
+import work.baiyun.chronicdiseaseapp.service.UserBindingService;
+import work.baiyun.chronicdiseaseapp.model.vo.BaseQueryRequest;
+import work.baiyun.chronicdiseaseapp.model.vo.UserBindingResponse;
 import work.baiyun.chronicdiseaseapp.util.SecurityUtils;
 import work.baiyun.chronicdiseaseapp.util.SecurityUtils;
 
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Field;
@@ -33,6 +39,12 @@ public class HealthDataCriticalAlertAspect {
     @Autowired
     @Autowired
     private CriticalValueMapper criticalValueMapper;
     private CriticalValueMapper criticalValueMapper;
 
 
+    @Autowired
+    private MessageService messageService;
+
+    @Autowired
+    private UserBindingService userBindingService;
+
     @Pointcut("execution(* work.baiyun.chronicdiseaseapp.controller..*(..)) && (execution(* *..add*(..)) || execution(* *..upload*(..)) || execution(* *..create*(..)))")
     @Pointcut("execution(* work.baiyun.chronicdiseaseapp.controller..*(..)) && (execution(* *..add*(..)) || execution(* *..upload*(..)) || execution(* *..create*(..)))")
     public void uploadMethods() {}
     public void uploadMethods() {}
 
 
@@ -105,6 +117,9 @@ public class HealthDataCriticalAlertAspect {
             mapKeyToCode.put("rate", "heartRate");
             mapKeyToCode.put("rate", "heartRate");
             mapKeyToCode.put("hr", "heartRate");
             mapKeyToCode.put("hr", "heartRate");
 
 
+            // collect warnings per measurement, we'll aggregate and send one system-anomaly message if any
+            Map<String, Map<String, Object>> warnings = new HashMap<>();
+
             // For each measurement, load threshold and compare
             // For each measurement, load threshold and compare
             for (Map.Entry<String, BigDecimal> e : measurements.entrySet()) {
             for (Map.Entry<String, BigDecimal> e : measurements.entrySet()) {
                 String key = e.getKey();
                 String key = e.getKey();
@@ -127,9 +142,91 @@ public class HealthDataCriticalAlertAspect {
                 if (warn) {
                 if (warn) {
                     logger.warn("危急值警告: userId={} type={} measured={} below={} above={} controllerMethod={}",
                     logger.warn("危急值警告: userId={} type={} measured={} below={} above={} controllerMethod={}",
                             userId, code, measured, low, high, pjp.getSignature().toShortString());
                             userId, code, measured, low, high, pjp.getSignature().toShortString());
+                    // add to aggregated anomaly data
+                    Map<String, Object> info = new HashMap<>();
+                    info.put("measured", measured);
+                    info.put("below", low);
+                    info.put("above", high);
+                    info.put("detectedBy", pjp.getSignature().toShortString());
+                    warnings.put(code, info);
                 }
                 }
             }
             }
+            
+            // If any warnings collected, send a single system anomaly message
+            if (!warnings.isEmpty()) {
+                // Build a richer anomaly payload containing original patient id and detected anomalies
+                Map<String, Object> anomalyPayload = new HashMap<>();
+                anomalyPayload.put("patientId", userId);
+                anomalyPayload.put("anomalies", warnings);
+                anomalyPayload.put("detectedAt", java.time.LocalDateTime.now().toString());
+
+                // prepare base request
+                SystemAnomalySendRequest baseReq = new SystemAnomalySendRequest();
+                baseReq.setAnomalyData(anomalyPayload);
+                baseReq.setType(MessageType.SYSTEM_ANOMALY);
 
 
+                // Collect recipients: patient, bound family, bound doctors
+                try {
+                    // 1) send to patient (uploader)
+                    try {
+                        SystemAnomalySendRequest reqPatient = new SystemAnomalySendRequest();
+                        reqPatient.setPatientId(userId);
+                        reqPatient.setAnomalyData(anomalyPayload);
+                        reqPatient.setType(MessageType.SYSTEM_ANOMALY);
+                        String msgId = messageService.sendSystemAnomalyMessage(reqPatient);
+                        logger.info("Sent system anomaly message id={} to patientId={} anomalies={}", msgId, userId, warnings.keySet());
+                    } catch (Exception ex) {
+                        logger.error("Failed to send system anomaly message to patient {}", userId, ex);
+                    }
+
+                    // 2) send to bound family
+                    try {
+                        java.util.List<Long> familyIds = userBindingService.getBoundFamilyIds(userId);
+                        if (familyIds != null) {
+                            for (Long famId : familyIds) {
+                                try {
+                                    SystemAnomalySendRequest reqFam = new SystemAnomalySendRequest();
+                                    reqFam.setPatientId(famId);
+                                    reqFam.setAnomalyData(anomalyPayload);
+                                    reqFam.setType(MessageType.SYSTEM_ANOMALY);
+                                    String msgIdFam = messageService.sendSystemAnomalyMessage(reqFam);
+                                    logger.info("Sent system anomaly message id={} to familyId={} for patient={} anomalies={}", msgIdFam, famId, userId, warnings.keySet());
+                                } catch (Exception ex) {
+                                    logger.error("Failed to send system anomaly message to family {}", famId, ex);
+                                }
+                            }
+                        }
+                    } catch (Exception ex) {
+                        logger.warn("Failed to fetch bound family ids for user {}", userId, ex);
+                    }
+
+                    // 3) send to bound doctors (use listBindingsByPatient to find DOCTOR bindings)
+                    try {
+                        BaseQueryRequest bqr = new BaseQueryRequest();
+                        // call service to get bindings (we only need the first page)
+                        com.baomidou.mybatisplus.extension.plugins.pagination.Page<UserBindingResponse> page = userBindingService.listBindingsByPatient(userId, "DOCTOR", bqr);
+                        if (page != null && page.getRecords() != null) {
+                            for (UserBindingResponse ub : page.getRecords()) {
+                                try {
+                                    Long docId = Long.valueOf(ub.getBoundUserId());
+                                    SystemAnomalySendRequest reqDoc = new SystemAnomalySendRequest();
+                                    reqDoc.setPatientId(docId);
+                                    reqDoc.setAnomalyData(anomalyPayload);
+                                    reqDoc.setType(MessageType.SYSTEM_ANOMALY);
+                                    String msgIdDoc = messageService.sendSystemAnomalyMessage(reqDoc);
+                                    logger.info("Sent system anomaly message id={} to doctorId={} for patient={} anomalies={}", msgIdDoc, docId, userId, warnings.keySet());
+                                } catch (Exception ex) {
+                                    logger.error("Failed to send system anomaly message to a bound doctor record {}", ub, ex);
+                                }
+                            }
+                        }
+                    } catch (Exception ex) {
+                        logger.warn("Failed to fetch bound doctor bindings for user {}", userId, ex);
+                    }
+                } catch (Exception ex) {
+                    logger.error("Failed to send aggregated system anomaly messages", ex);
+                }
+            }
         } catch (Exception ex) {
         } catch (Exception ex) {
             logger.error("HealthDataCriticalAlertAspect failed to check critical values", ex);
             logger.error("HealthDataCriticalAlertAspect failed to check critical values", ex);
         }
         }

+ 93 - 7
src/main/java/work/baiyun/chronicdiseaseapp/service/impl/MessageServiceImpl.java

@@ -185,10 +185,96 @@ public class MessageServiceImpl implements MessageService {
 
 
     @Override
     @Override
     public String sendSystemAnomalyMessage(SystemAnomalySendRequest request) {
     public String sendSystemAnomalyMessage(SystemAnomalySendRequest request) {
+        // receiver id is stored in request.getPatientId()
+        Long receiverId = request.getPatientId();
+
+        // anomalyData expected to be a Map with keys: patientId, anomalies, detectedAt
+        Object anomalyObj = request.getAnomalyData();
+        Long origPatientId = null;
+        Map<String, Object> anomaliesMap = null;
+        String detectedAt = null;
+        if (anomalyObj instanceof Map) {
+            try {
+                Map<?, ?> m = (Map<?, ?>) anomalyObj;
+                Object pid = m.get("patientId");
+                if (pid instanceof Number) origPatientId = ((Number) pid).longValue();
+                else if (pid instanceof String) origPatientId = Long.valueOf((String) pid);
+                Object am = m.get("anomalies");
+                if (am instanceof Map) anomaliesMap = (Map<String, Object>) am;
+                Object da = m.get("detectedAt");
+                if (da != null) detectedAt = String.valueOf(da);
+            } catch (Exception ignored) {}
+        }
+
+        // Fetch user infos
+        UserInfo origPatient = null;
+        if (origPatientId != null) origPatient = userInfoMapper.selectById(origPatientId);
+        UserInfo receiver = userInfoMapper.selectById(receiverId);
+
+        // Build human-friendly anomaly description list
+        java.util.List<String> parts = new java.util.ArrayList<>();
+        if (anomaliesMap != null) {
+            java.util.Map<String, String> label = new java.util.HashMap<>();
+            label.put("height", "身高");
+            label.put("weight", "体重");
+            label.put("bmi", "BMI");
+            label.put("systolic", "收缩压");
+            label.put("diastolic", "舒张压");
+            label.put("fasting", "空腹血糖");
+            label.put("random", "随机血糖");
+            label.put("heartRate", "心率");
+
+            for (Map.Entry<String, Object> en : anomaliesMap.entrySet()) {
+                try {
+                    String code = en.getKey();
+                    Object infoObj = en.getValue();
+                    if (!(infoObj instanceof Map)) continue;
+                    Map<?, ?> info = (Map<?, ?>) infoObj;
+                    Object measuredO = info.get("measured");
+                    Object belowO = info.get("below");
+                    Object aboveO = info.get("above");
+                    java.math.BigDecimal measured = measuredO instanceof Number ? new java.math.BigDecimal(((Number) measuredO).toString()) : (measuredO != null ? new java.math.BigDecimal(String.valueOf(measuredO)) : null);
+                    java.math.BigDecimal below = belowO instanceof Number ? new java.math.BigDecimal(((Number) belowO).toString()) : (belowO != null ? new java.math.BigDecimal(String.valueOf(belowO)) : null);
+                    java.math.BigDecimal above = aboveO instanceof Number ? new java.math.BigDecimal(((Number) aboveO).toString()) : (aboveO != null ? new java.math.BigDecimal(String.valueOf(aboveO)) : null);
+
+                    String name = label.getOrDefault(code, code);
+                    if (measured != null) {
+                        String desc = name + " " + measured.stripTrailingZeros().toPlainString();
+                        if (below != null && measured.compareTo(below) < 0) {
+                            desc += " 低于设定下限 " + below.stripTrailingZeros().toPlainString();
+                        } else if (above != null && measured.compareTo(above) > 0) {
+                            desc += " 超过设定上限 " + above.stripTrailingZeros().toPlainString();
+                        }
+                        parts.add(desc);
+                    }
+                } catch (Exception ignored) {}
+            }
+        }
+
+        String anomalySummary = parts.isEmpty() ? "若干异常指标" : String.join(";", parts);
+        String patientLabel = origPatient != null && origPatient.getNickname() != null ? origPatient.getNickname() : (origPatientId != null ? String.valueOf(origPatientId) : "患者");
+        String timeLabel = detectedAt != null ? detectedAt : LocalDateTime.now().toString();
+
+        // Compose message differently based on receiver role
+        String content;
+        try {
+            if (receiver != null && receiver.getRole() != null && receiver.getRole().getCode() == work.baiyun.chronicdiseaseapp.enums.PermissionGroup.DOCTOR.getCode()) {
+                content = String.format("医生,您的患者 %s(患者id:%s)在 %s 上传了异常的 %s,请尽快处理。", patientLabel, origPatientId != null ? origPatientId : "-", timeLabel, anomalySummary);
+            } else if (receiver != null && receiver.getRole() != null && receiver.getRole().getCode() == work.baiyun.chronicdiseaseapp.enums.PermissionGroup.PATIENT.getCode()) {
+                content = String.format("您上传的 %s 属于危急异常数据,请尽快与医生联系或前往就近医院诊疗。", anomalySummary);
+            } else if (receiver != null && receiver.getRole() != null && receiver.getRole().getCode() == work.baiyun.chronicdiseaseapp.enums.PermissionGroup.PATIENT_FAMILY.getCode()) {
+                content = String.format("您绑定的患者 %s(患者id:%s)在 %s 上传了异常的 %s,请尽快带其就近就医并联系主治医生。", patientLabel, origPatientId != null ? origPatientId : "-", timeLabel, anomalySummary);
+            } else {
+                content = "检测到异常数据: " + (request.getAnomalyData() != null ? request.getAnomalyData().toString() : "");
+            }
+        } catch (Exception ex) {
+            content = "检测到异常数据: " + (request.getAnomalyData() != null ? request.getAnomalyData().toString() : "");
+        }
+
         MessagePO message = new MessagePO();
         MessagePO message = new MessagePO();
         message.setSenderId(0L);
         message.setSenderId(0L);
-        message.setReceiverId(request.getPatientId());
-        message.setContent("检测到异常数据: " + request.getAnomalyData().toString()); // 简化
+        message.setReceiverId(receiverId);
+        message.setContent(content);
         message.setContentFormat("PLAIN");
         message.setContentFormat("PLAIN");
         message.setType(request.getType());
         message.setType(request.getType());
         message.setNotifyPopup((byte) 1); // 弹窗
         message.setNotifyPopup((byte) 1); // 弹窗
@@ -199,7 +285,7 @@ public class MessageServiceImpl implements MessageService {
         // 推送(先检查患者提醒设置)
         // 推送(先检查患者提醒设置)
         try {
         try {
             boolean canSendAnomaly = true;
             boolean canSendAnomaly = true;
-            PatientReminder pr = patientReminderMapper.selectByPatientUserId(request.getPatientId());
+            PatientReminder pr = patientReminderMapper.selectByPatientUserId(receiverId);
             if (pr != null) {
             if (pr != null) {
                 if (pr.getNotificationEnabled() == null || pr.getNotificationEnabled() != 1) {
                 if (pr.getNotificationEnabled() == null || pr.getNotificationEnabled() != 1) {
                     canSendAnomaly = false;
                     canSendAnomaly = false;
@@ -209,13 +295,13 @@ public class MessageServiceImpl implements MessageService {
                 }
                 }
             }
             }
             if (canSendAnomaly) {
             if (canSendAnomaly) {
-                pushService.sendSubscribeMessage(request.getPatientId(), message);
+                pushService.sendSubscribeMessage(receiverId, message);
             } else {
             } else {
-                logger.info("异常通知订阅消息已被患者设置禁止,跳过推送,patientId={}, messageId={}", request.getPatientId(), message.getId());
+                logger.info("异常通知订阅消息已被接收者设置禁止,跳过推送,receiverId={}, messageId={}", receiverId, message.getId());
             }
             }
         } catch (Exception e) {
         } catch (Exception e) {
-            logger.warn("检查患者提醒设置失败,异常通知仍尝试推送,patientId={},err={}", request.getPatientId(), e.getMessage());
-            pushService.sendSubscribeMessage(request.getPatientId(), message);
+            logger.warn("检查接收者提醒设置失败,异常通知仍尝试推送,receiverId={},err={}", receiverId, e.getMessage());
+            pushService.sendSubscribeMessage(receiverId, message);
         }
         }
 
 
         logger.info("[MessageOperation] action=system_anomaly_send, messageId={}", message.getId());
         logger.info("[MessageOperation] action=system_anomaly_send, messageId={}", message.getId());