|
@@ -0,0 +1,144 @@
|
|
|
|
|
+package com.etotem.cfc.service;
|
|
|
|
|
+
|
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
|
|
+import com.etotem.cfc.entity.Child;
|
|
|
|
|
+import com.etotem.cfc.entity.User;
|
|
|
|
|
+import com.etotem.cfc.mapper.ChildMapper;
|
|
|
|
|
+import com.etotem.cfc.mapper.UserMapper;
|
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
|
|
+
|
|
|
|
|
+import javax.annotation.Resource;
|
|
|
|
|
+import java.util.HashMap;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+import java.util.Map;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 微信危机告警模板消息服务 —— 当孩子产生情绪危机告警时,
|
|
|
|
|
+ * 通过微信模板消息通知同家庭的家长。
|
|
|
|
|
+ */
|
|
|
|
|
+@Slf4j
|
|
|
|
|
+@Service
|
|
|
|
|
+public class WechatAlertTemplateService {
|
|
|
|
|
+
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private ChildMapper childMapper;
|
|
|
|
|
+
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private UserMapper userMapper;
|
|
|
|
|
+
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private WechatService wechatService;
|
|
|
|
|
+
|
|
|
|
|
+ @Value("${wechat.alert-template-id:}")
|
|
|
|
|
+ private String alertTemplateId;
|
|
|
|
|
+
|
|
|
|
|
+ private static final Map<String, String> ALERT_TYPE_ZH = new HashMap<>();
|
|
|
|
|
+ private static final Map<String, String> SEVERITY_ZH = new HashMap<>();
|
|
|
|
|
+
|
|
|
|
|
+ static {
|
|
|
|
|
+ ALERT_TYPE_ZH.put("crisis_keyword", "危机关键词");
|
|
|
|
|
+ ALERT_TYPE_ZH.put("consecutive_low_mood", "持续情绪低落");
|
|
|
|
|
+ ALERT_TYPE_ZH.put("sharp_decline", "情绪锐降");
|
|
|
|
|
+
|
|
|
|
|
+ SEVERITY_ZH.put("high", "高风险");
|
|
|
|
|
+ SEVERITY_ZH.put("medium", "中风险");
|
|
|
|
|
+ SEVERITY_ZH.put("low", "低风险");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 向孩子的家长发送危机告警模板消息
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param childId 孩子ID
|
|
|
|
|
+ * @param alertType 告警类型 (crisis_keyword / consecutive_low_mood / sharp_decline)
|
|
|
|
|
+ * @param severity 严重程度 (high / medium / low)
|
|
|
|
|
+ * @param title 告警标题
|
|
|
|
|
+ * @param message 告警内容(keyword4 会截断至20字符以内)
|
|
|
|
|
+ */
|
|
|
|
|
+ public void sendCrisisAlertToParents(Long childId, String alertType, String severity,
|
|
|
|
|
+ String title, String message) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 测试模式:仅记录日志,不实际发送
|
|
|
|
|
+ if (wechatService.isTestMode()) {
|
|
|
|
|
+ log.info("测试模式:跳过危机告警模板消息, childId={}, alertType={}, severity={}",
|
|
|
|
|
+ childId, alertType, severity);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 检查模板ID是否配置
|
|
|
|
|
+ if (alertTemplateId == null || alertTemplateId.isEmpty()) {
|
|
|
|
|
+ log.warn("微信告警模板ID未配置,跳过发送");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 查找孩子信息
|
|
|
|
|
+ Child child = childMapper.selectById(childId);
|
|
|
|
|
+ if (child == null) {
|
|
|
|
|
+ log.warn("孩子不存在, childId={}", childId);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String childNickname = child.getNickname() != null ? child.getNickname() : "孩子";
|
|
|
|
|
+ Long familyId = child.getFamilyId();
|
|
|
|
|
+ if (familyId == null) {
|
|
|
|
|
+ log.warn("孩子未关联家庭, childId={}", childId);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 查找同家庭的家长
|
|
|
|
|
+ List<User> parents = userMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<User>()
|
|
|
|
|
+ .eq(User::getFamilyId, familyId)
|
|
|
|
|
+ .eq(User::getRole, "parent")
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if (parents.isEmpty()) {
|
|
|
|
|
+ log.info("未找到同家庭家长, familyId={}", familyId);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 构建模板关键词
|
|
|
|
|
+ String alertTypeZh = ALERT_TYPE_ZH.getOrDefault(alertType, alertType);
|
|
|
|
|
+ String severityZh = SEVERITY_ZH.getOrDefault(severity, severity);
|
|
|
|
|
+ String truncatedMessage = truncate(message, 20);
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> keywordMap = new HashMap<>();
|
|
|
|
|
+ keywordMap.put("keyword1", childNickname);
|
|
|
|
|
+ keywordMap.put("keyword2", alertTypeZh);
|
|
|
|
|
+ keywordMap.put("keyword3", severityZh);
|
|
|
|
|
+ keywordMap.put("keyword4", truncatedMessage);
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 向每位有openid的家长发送模板消息
|
|
|
|
|
+ int sentCount = 0;
|
|
|
|
|
+ for (User parent : parents) {
|
|
|
|
|
+ String openid = parent.getOpenid();
|
|
|
|
|
+ if (openid != null && !openid.isEmpty()) {
|
|
|
|
|
+ wechatService.sendTemplateMessage(openid, alertTemplateId, keywordMap);
|
|
|
|
|
+ sentCount++;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ log.info("危机告警模板消息发送完成, childId={}, parents={}, sent={}",
|
|
|
|
|
+ childId, parents.size(), sentCount);
|
|
|
|
|
+
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ // 告警创建不能因通知失败而中断,仅记录错误日志
|
|
|
|
|
+ log.error("发送危机告警模板消息异常, childId={}, alertType={}", childId, alertType, e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 截断字符串至指定最大长度,超长时追加省略号
|
|
|
|
|
+ */
|
|
|
|
|
+ private String truncate(String str, int maxLen) {
|
|
|
|
|
+ if (str == null) {
|
|
|
|
|
+ return "";
|
|
|
|
|
+ }
|
|
|
|
|
+ if (str.length() <= maxLen) {
|
|
|
|
|
+ return str;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 截断并保留1字符给省略号
|
|
|
|
|
+ return str.substring(0, maxLen - 1) + "…";
|
|
|
|
|
+ }
|
|
|
|
|
+}
|