package com.project.interaction.utils; import cn.hutool.core.date.DateUtil; import com.github.tingyugetc520.ali.dingtalk.api.DtService; import com.github.tingyugetc520.ali.dingtalk.bean.message.DtCorpConversationMessage; import com.github.tingyugetc520.ali.dingtalk.bean.message.DtCorpConversationMsgSendResult; import com.github.tingyugetc520.ali.dingtalk.bean.message.DtMessage; import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.time.Duration; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.TimeUnit; @Slf4j @Component public class NotifyUtil { @Autowired private DtService dtService; @Autowired private RedissonClient redissonClient; // 钉钉通知相关配置 private final List WARN_USER_LIST = Lists.newArrayList( "283712335120874575", "01231011386731976125", "590204300932000212" ); // 防重复缓存 key 前缀 private static final String WARN_LOCK_KEY_PREFIX = "question:gen:warn:lock:"; // 重复发送间隔:1 小时(3600 秒) private static final long WARN_LOCK_EXPIRE_SECONDS = 3600L; public void notify(Long taskId,Long clusterId) { if (taskId == null || clusterId == null) { return; } // 构建 Redis 缓存 key(按 taskId 唯一) String cacheKey = WARN_LOCK_KEY_PREFIX + taskId; // 核心逻辑:使用原子操作 setIfAbsent 防止并发重复发送 // 返回 true 表示成功设置(之前不存在),返回 false 表示 1 小时内已发送过 boolean acquired = redissonClient.getBucket(cacheKey).setIfAbsent( "SEND_SUCCESS", Duration.ofSeconds(WARN_LOCK_EXPIRE_SECONDS) ); if (!acquired) { return; } try { String now = DateUtil.now(); String markdown = String.format("### 【告警】算法生题 API 调用失败通知 \n\n") + String.format("## 核心信息\n") + String.format("- 考试任务 ID:%d\n", taskId) + String.format("- 知识点簇 ID:%d\n", clusterId) + String.format("- 当前时间:%s\n\n", now) + String.format("## 异常说明\n") + String.format("算法服务生成题目时调用失败,可能导致考试题目不完整或无法正常作答,请尽快排查以下问题:\n") + String.format("- 算法服务是否正常运行\n"); DtCorpConversationMessage corpConversationMessage = DtCorpConversationMessage.builder() .agentId(dtService.getDtConfigStorage().getAgentId()) .userIds(WARN_USER_LIST) .msg(DtMessage.MARKDOWN() .content("算法生题 API 调用失败通知") .text(markdown) .build()) .build(); DtCorpConversationMsgSendResult result = dtService.getCorpConversationMsgService().send(corpConversationMessage); if (result.getErrCode() == 0) { log.info(">>> [算法生题 API 调用失败通知] 成功发送通知,考试任务 ID:{} ,知识点簇 ID:{}", taskId, clusterId); } else { log.error(">>> [算法生题 API 调用失败通知] 发送失败,考试任务 ID:{} ,知识点簇 ID:{}, 错误码:{}, 错误信息:{}", taskId, clusterId, result.getErrCode(), result.getErrMsg()); } } catch (Exception e) { log.error(">>> [算法生题 API 调用失败通知] 钉钉接口调用异常,考试任务 ID:{} ,知识点簇 ID:{}, 原因:{}",taskId, clusterId, e.getMessage()); } } }