提交
This commit is contained in:
@@ -1,7 +1,4 @@
|
|||||||
package com.tashow.cloud.app.config;
|
package com.tashow.cloud.app.config;
|
||||||
|
|
||||||
import com.tashow.cloud.sdk.feishu.client.FeiShuAlertClient;
|
|
||||||
import jakarta.annotation.PostConstruct;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
@@ -12,11 +9,8 @@ import org.springframework.data.redis.core.StringRedisTemplate;
|
|||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
public class FeiShuClientConfig {
|
public class FeiShuClientConfig {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private StringRedisTemplate stringRedisTemplate;
|
private StringRedisTemplate stringRedisTemplate;
|
||||||
|
|
||||||
|
|
||||||
/* @PostConstruct
|
/* @PostConstruct
|
||||||
public void initFeiShuClient() {
|
public void initFeiShuClient() {
|
||||||
FeiShuAlertClient.setRedisTemplate(stringRedisTemplate);
|
FeiShuAlertClient.setRedisTemplate(stringRedisTemplate);
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
package com.tashow.cloud.app.config;
|
|
||||||
|
|
||||||
import com.tashow.cloud.app.service.feishu.FeiShuCardDataService;
|
|
||||||
import com.tashow.cloud.sdk.feishu.client.FeiShuAlertClient;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import jakarta.annotation.PostConstruct;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 飞书配置类
|
|
||||||
* 用于初始化飞书SDK与应用层的集成
|
|
||||||
*/
|
|
||||||
@Configuration
|
|
||||||
public class FeishuConfig {
|
|
||||||
|
|
||||||
private final FeiShuAlertClient feiShuAlertClient;
|
|
||||||
private final FeiShuCardDataService feiShuCardDataService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public FeishuConfig(FeiShuAlertClient feiShuAlertClient, FeiShuCardDataService feiShuCardDataService) {
|
|
||||||
this.feiShuAlertClient = feiShuAlertClient;
|
|
||||||
this.feiShuCardDataService = feiShuCardDataService;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.tashow.cloud.app.controller;
|
package com.tashow.cloud.app.controller;
|
||||||
import cn.hutool.json.JSONObject;
|
import cn.hutool.json.JSONObject;
|
||||||
import cn.hutool.json.JSONUtil;
|
|
||||||
import com.lark.oapi.core.utils.Decryptor;
|
import com.lark.oapi.core.utils.Decryptor;
|
||||||
import com.tashow.cloud.app.service.feishu.FeiShuCardDataService;
|
import com.tashow.cloud.app.service.feishu.FeiShuCardDataService;
|
||||||
import com.tashow.cloud.sdk.feishu.client.FeiShuAlertClient;
|
import com.tashow.cloud.sdk.feishu.client.FeiShuAlertClient;
|
||||||
@@ -16,7 +15,8 @@ import java.util.Map;
|
|||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
public class FeishuController {
|
public class FeishuController {
|
||||||
private final Logger log = LoggerFactory.getLogger(FeishuController.class);
|
private static final Logger log = LoggerFactory.getLogger(FeishuController.class);
|
||||||
|
private static final String ACTION_COMPLETE_ALARM = "complete_alarm";
|
||||||
|
|
||||||
private final FeiShuAlertClient feiShuAlertClient;
|
private final FeiShuAlertClient feiShuAlertClient;
|
||||||
private final FeiShuCardDataService feiShuCardDataService;
|
private final FeiShuCardDataService feiShuCardDataService;
|
||||||
@@ -29,7 +29,6 @@ public class FeishuController {
|
|||||||
this.feiShuAlertClient = feiShuAlertClient;
|
this.feiShuAlertClient = feiShuAlertClient;
|
||||||
this.feiShuCardDataService = feiShuCardDataService;
|
this.feiShuCardDataService = feiShuCardDataService;
|
||||||
this.larkConfig = larkConfig;
|
this.larkConfig = larkConfig;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping("/card1")
|
@RequestMapping("/card1")
|
||||||
@@ -39,23 +38,33 @@ public class FeishuController {
|
|||||||
if (data.containsKey("app_id") && data.containsKey("action")) {
|
if (data.containsKey("app_id") && data.containsKey("action")) {
|
||||||
JSONObject action = data.getJSONObject("action");
|
JSONObject action = data.getJSONObject("action");
|
||||||
JSONObject value = action.getJSONObject("value");
|
JSONObject value = action.getJSONObject("value");
|
||||||
if (value != null && "complete_alarm".equals(value.getStr("action"))) {
|
if (value != null && ACTION_COMPLETE_ALARM.equals(value.getStr("action"))) {
|
||||||
String messageId = data.getStr("open_message_id");
|
String messageId = data.getStr("open_message_id");
|
||||||
|
|
||||||
Map<String, Object> templateData = feiShuCardDataService.getCardData(messageId);
|
Map<String, Object> templateData = feiShuCardDataService.getCardData(messageId);
|
||||||
log.info("从Redis获取的模板数据: {}", templateData);
|
return feiShuAlertClient.buildCardWithData(larkConfig.getSuccessCards(), templateData);
|
||||||
return feiShuAlertClient.buildCardWithData("AAqdp4Mrvf2V9", templateData);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.containsKey("encrypt")) {
|
if (data.containsKey("encrypt")) {
|
||||||
Decryptor decryptor = new Decryptor(larkConfig.getEncryptKey());
|
Decryptor decryptor = new Decryptor(larkConfig.getEncryptKey());
|
||||||
String encrypt = decryptor.decrypt(data.getStr("encrypt"));
|
return decryptor.decrypt(data.getStr("encrypt"));
|
||||||
return encrypt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "{}";
|
return "{}";
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("卡片处理异常", e);
|
log.error("卡片处理异常", e);
|
||||||
return "{\"code\":1,\"msg\":\"处理异常: " + e.getMessage() + "\"}";
|
return "{\"code\":1,\"msg\":\"处理异常: " + e.getMessage() + "\"}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送并存储卡片消息
|
||||||
|
*/
|
||||||
|
public String sendAndStoreCardMessage(String chatId, String templateId, Map<String, Object> templateData) throws Exception {
|
||||||
|
String messageId = feiShuAlertClient.sendCardMessage(chatId, templateId, templateData);
|
||||||
|
if (messageId != null) {
|
||||||
|
feiShuCardDataService.saveCardData(messageId, templateData);
|
||||||
|
}
|
||||||
|
return messageId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ public class BuriedPointConfiguration implements WebMvcConfigurer {
|
|||||||
"/v3/api-docs/**",
|
"/v3/api-docs/**",
|
||||||
"/webjars/**",
|
"/webjars/**",
|
||||||
"/static/**",
|
"/static/**",
|
||||||
|
"/card1",
|
||||||
"/error"
|
"/error"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
package com.tashow.cloud.app.mq.consumer.buriedPoint;
|
package com.tashow.cloud.app.mq.consumer.buriedPoint;
|
||||||
|
|
||||||
import com.tashow.cloud.sdk.feishu.client.FeiShuAlertClient;
|
|
||||||
import com.tashow.cloud.app.mapper.BuriedPointMapper;
|
import com.tashow.cloud.app.mapper.BuriedPointMapper;
|
||||||
import com.tashow.cloud.app.mapper.BuriedPointFailRecordMapper;
|
import com.tashow.cloud.app.mapper.BuriedPointFailRecordMapper;
|
||||||
import com.tashow.cloud.app.mq.message.BuriedMessages;
|
|
||||||
import com.tashow.cloud.app.model.BuriedPoint;
|
import com.tashow.cloud.app.model.BuriedPoint;
|
||||||
import com.tashow.cloud.app.model.BuriedPointFailRecord;
|
import com.tashow.cloud.app.model.BuriedPointFailRecord;
|
||||||
import com.tashow.cloud.sdk.feishu.config.LarkConfig;
|
import com.tashow.cloud.app.mq.message.BuriedMessages;
|
||||||
import com.tashow.cloud.sdk.feishu.util.ChartImageGenerator;
|
import com.tashow.cloud.app.service.feishu.BuriedPointMonitorService;
|
||||||
import com.rabbitmq.client.Channel;
|
import com.rabbitmq.client.Channel;
|
||||||
import com.tashow.cloud.mq.rabbitmq.consumer.AbstractRabbitMQConsumer;
|
import com.tashow.cloud.mq.rabbitmq.consumer.AbstractRabbitMQConsumer;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
@@ -15,12 +13,11 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
|
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
|
||||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||||
import org.springframework.amqp.support.AmqpHeaders;
|
import org.springframework.amqp.support.AmqpHeaders;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.messaging.handler.annotation.Header;
|
import org.springframework.messaging.handler.annotation.Header;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.*;
|
import java.util.Date;
|
||||||
import org.springframework.dao.DuplicateKeyException;
|
import org.springframework.dao.DuplicateKeyException;
|
||||||
import com.tashow.cloud.common.util.json.JsonUtils;
|
import com.tashow.cloud.common.util.json.JsonUtils;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
@@ -36,8 +33,7 @@ public class BuriedPointConsumer extends AbstractRabbitMQConsumer<BuriedMessages
|
|||||||
|
|
||||||
private final BuriedPointMapper buriedPointMapper;
|
private final BuriedPointMapper buriedPointMapper;
|
||||||
private final BuriedPointFailRecordMapper buriedPointFailRecordMapper;
|
private final BuriedPointFailRecordMapper buriedPointFailRecordMapper;
|
||||||
private final FeiShuAlertClient feiShuAlertClient;
|
private final BuriedPointMonitorService buriedPointMonitorService;
|
||||||
private final LarkConfig larkConfig;
|
|
||||||
|
|
||||||
@Value("${spring.application.name:tashow-app}")
|
@Value("${spring.application.name:tashow-app}")
|
||||||
private String applicationName;
|
private String applicationName;
|
||||||
@@ -54,7 +50,6 @@ public class BuriedPointConsumer extends AbstractRabbitMQConsumer<BuriedMessages
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean processMessage(BuriedMessages message) {
|
public boolean processMessage(BuriedMessages message) {
|
||||||
// 消息处理
|
|
||||||
return saveToDatabase(message);
|
return saveToDatabase(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,19 +63,26 @@ public class BuriedPointConsumer extends AbstractRabbitMQConsumer<BuriedMessages
|
|||||||
return buriedPoint.getRetryCount() - 1;
|
return buriedPoint.getRetryCount() - 1;
|
||||||
}
|
}
|
||||||
return buriedPoint.getRetryCount();
|
return buriedPoint.getRetryCount();
|
||||||
} else {
|
|
||||||
String correlationId = String.valueOf(message.getId());
|
|
||||||
LambdaQueryWrapper<BuriedPointFailRecord> queryWrapper = new LambdaQueryWrapper<>();
|
|
||||||
queryWrapper.eq(BuriedPointFailRecord::getCorrelationId, correlationId);
|
|
||||||
BuriedPointFailRecord failRecord = buriedPointFailRecordMapper.selectOne(queryWrapper);
|
|
||||||
return failRecord != null ? failRecord.getRetryCount() : 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String correlationId = String.valueOf(message.getId());
|
||||||
|
BuriedPointFailRecord failRecord = findFailRecord(correlationId);
|
||||||
|
return failRecord != null ? failRecord.getRetryCount() : 0;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("[埋点消费者] 获取消息重试次数失败: {}", e.getMessage());
|
log.warn("[埋点消费者] 获取重试次数失败", e);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据关联ID查找失败记录
|
||||||
|
*/
|
||||||
|
private BuriedPointFailRecord findFailRecord(String correlationId) {
|
||||||
|
LambdaQueryWrapper<BuriedPointFailRecord> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
queryWrapper.eq(BuriedPointFailRecord::getCorrelationId, correlationId);
|
||||||
|
return buriedPointFailRecordMapper.selectOne(queryWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateMessageStatus(BuriedMessages message) {
|
public void updateMessageStatus(BuriedMessages message) {
|
||||||
try {
|
try {
|
||||||
@@ -90,11 +92,9 @@ public class BuriedPointConsumer extends AbstractRabbitMQConsumer<BuriedMessages
|
|||||||
buriedPoint.setUpdateTime(new Date());
|
buriedPoint.setUpdateTime(new Date());
|
||||||
buriedPoint.setRetryCount(message.getRetryCount());
|
buriedPoint.setRetryCount(message.getRetryCount());
|
||||||
buriedPointMapper.updateById(buriedPoint);
|
buriedPointMapper.updateById(buriedPoint);
|
||||||
log.debug("[埋点消费者] 已更新埋点状态, 事件ID: {}, 新状态: {}, 重试次数: {}",
|
|
||||||
message.getId(), message.getStatusCode(), message.getRetryCount());
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[埋点消费者] 更新埋点状态失败: {}, 错误: {}", message.getId(), e.getMessage(), e);
|
log.error("[埋点消费者] 更新状态失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,49 +103,81 @@ public class BuriedPointConsumer extends AbstractRabbitMQConsumer<BuriedMessages
|
|||||||
try {
|
try {
|
||||||
BuriedPoint buriedPoint = buriedPointMapper.selectByEventId(message.getId());
|
BuriedPoint buriedPoint = buriedPointMapper.selectByEventId(message.getId());
|
||||||
if (buriedPoint != null) {
|
if (buriedPoint != null) {
|
||||||
|
updateBuriedPointRetryCount(buriedPoint, message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String correlationId = String.valueOf(message.getId());
|
||||||
|
BuriedPointFailRecord failRecord = findFailRecord(correlationId);
|
||||||
|
if (failRecord != null) {
|
||||||
|
updateFailRecordRetryCount(failRecord, message);
|
||||||
|
} else {
|
||||||
|
saveToFailRecord(message, "");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[埋点消费者] 更新重试次数失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新埋点表中的重试次数
|
||||||
|
*/
|
||||||
|
private void updateBuriedPointRetryCount(BuriedPoint buriedPoint, BuriedMessages message) {
|
||||||
buriedPoint.setRetryCount(message.getRetryCount());
|
buriedPoint.setRetryCount(message.getRetryCount());
|
||||||
buriedPoint.setUpdateTime(new Date());
|
buriedPoint.setUpdateTime(new Date());
|
||||||
buriedPointMapper.updateById(buriedPoint);
|
buriedPointMapper.updateById(buriedPoint);
|
||||||
} else {
|
}
|
||||||
String correlationId = String.valueOf(message.getId());
|
|
||||||
LambdaQueryWrapper<BuriedPointFailRecord> queryWrapper = new LambdaQueryWrapper<>();
|
/**
|
||||||
queryWrapper.eq(BuriedPointFailRecord::getCorrelationId, correlationId);
|
* 更新失败记录表中的重试次数
|
||||||
BuriedPointFailRecord failRecord = buriedPointFailRecordMapper.selectOne(queryWrapper);
|
*/
|
||||||
if (failRecord != null) {
|
private void updateFailRecordRetryCount(BuriedPointFailRecord failRecord, BuriedMessages message) {
|
||||||
failRecord.setRetryCount(message.getRetryCount());
|
failRecord.setRetryCount(message.getRetryCount());
|
||||||
failRecord.setUpdateTime(new Date());
|
failRecord.setUpdateTime(new Date());
|
||||||
failRecord.setMessageContent(JsonUtils.toJsonString(message));
|
failRecord.setMessageContent(JsonUtils.toJsonString(message));
|
||||||
buriedPointFailRecordMapper.updateById(failRecord);
|
buriedPointFailRecordMapper.updateById(failRecord);
|
||||||
} else {
|
|
||||||
saveToFailRecord(message, "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("[埋点消费者] 更新重试次数失败: {}, 错误: {}", message.getId(), e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean saveToDatabase(BuriedMessages message) {
|
public boolean saveToDatabase(BuriedMessages message) {
|
||||||
try {
|
try {
|
||||||
log.debug("[埋点消费者] 准备保存埋点数据,事件ID: {}", message.getId());
|
|
||||||
BuriedPoint existingPoint = buriedPointMapper.selectByEventId(message.getId());
|
BuriedPoint existingPoint = buriedPointMapper.selectByEventId(message.getId());
|
||||||
if (existingPoint != null) {
|
if (existingPoint != null) {
|
||||||
|
return updateExistingBuriedPoint(existingPoint, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return createNewBuriedPoint(message);
|
||||||
|
} catch (DuplicateKeyException e) {
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[埋点消费者] 保存数据失败", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新已存在的埋点记录
|
||||||
|
*/
|
||||||
|
private boolean updateExistingBuriedPoint(BuriedPoint existingPoint, BuriedMessages message) {
|
||||||
existingPoint.setStatus(message.getStatusCode());
|
existingPoint.setStatus(message.getStatusCode());
|
||||||
existingPoint.setUpdateTime(new Date());
|
existingPoint.setUpdateTime(new Date());
|
||||||
|
|
||||||
if (message.getRetryCount() != null) {
|
if (message.getRetryCount() != null) {
|
||||||
int newRetryCount = Math.max(existingPoint.getRetryCount(), message.getRetryCount());
|
int newRetryCount = Math.max(existingPoint.getRetryCount(), message.getRetryCount());
|
||||||
existingPoint.setRetryCount(newRetryCount);
|
existingPoint.setRetryCount(newRetryCount);
|
||||||
message.setRetryCount(newRetryCount);
|
message.setRetryCount(newRetryCount);
|
||||||
}
|
}
|
||||||
int result = buriedPointMapper.updateById(existingPoint);
|
|
||||||
return result > 0;
|
return buriedPointMapper.updateById(existingPoint) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建新的埋点记录
|
||||||
|
*/
|
||||||
|
private boolean createNewBuriedPoint(BuriedMessages message) {
|
||||||
BuriedPoint buriedPoint = new BuriedPoint();
|
BuriedPoint buriedPoint = new BuriedPoint();
|
||||||
buriedPoint.setEventId(message.getId());
|
buriedPoint.setEventId(message.getId());
|
||||||
buriedPoint.setEventTime(System.currentTimeMillis());
|
buriedPoint.setEventTime(System.currentTimeMillis());
|
||||||
|
|
||||||
buriedPoint.setUserId(message.getUserId());
|
buriedPoint.setUserId(message.getUserId());
|
||||||
buriedPoint.setEventType(message.getEventType());
|
buriedPoint.setEventType(message.getEventType());
|
||||||
buriedPoint.setService(applicationName);
|
buriedPoint.setService(applicationName);
|
||||||
@@ -161,62 +193,57 @@ public class BuriedPointConsumer extends AbstractRabbitMQConsumer<BuriedMessages
|
|||||||
buriedPoint.setCreateTime(new Date());
|
buriedPoint.setCreateTime(new Date());
|
||||||
buriedPoint.setUpdateTime(new Date());
|
buriedPoint.setUpdateTime(new Date());
|
||||||
|
|
||||||
log.debug("[埋点消费者] 埋点实体数据: eventId={}, eventType={}, userId={}, service={}, method={}, status={}, retryCount={}",
|
|
||||||
buriedPoint.getEventId(), buriedPoint.getEventType(), buriedPoint.getUserId(),
|
|
||||||
buriedPoint.getService(), buriedPoint.getMethod(), buriedPoint.getStatus(),
|
|
||||||
buriedPoint.getRetryCount());
|
|
||||||
|
|
||||||
buriedPointMapper.insert(buriedPoint);
|
buriedPointMapper.insert(buriedPoint);
|
||||||
log.info("[埋点消费者] 埋点数据已保存到数据库, 事件ID: {}, 状态: {}", message.getId(), message.getStatusCode());
|
|
||||||
return true;
|
return true;
|
||||||
} catch (DuplicateKeyException e) {
|
|
||||||
log.warn("[埋点消费者] 埋点数据已存在, 事件ID: {}", message.getId());
|
|
||||||
return true; // 数据已存在也视为成功
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("[埋点消费者] 保存埋点数据到数据库失败, 事件ID: {}, 错误: {}", message.getId(), e.getMessage(), e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void saveToFailRecord(BuriedMessages message, String cause) {
|
public void saveToFailRecord(BuriedMessages message, String cause) {
|
||||||
try {
|
try {
|
||||||
String correlationId = String.valueOf(message.getId());
|
String correlationId = String.valueOf(message.getId());
|
||||||
LambdaQueryWrapper<BuriedPointFailRecord> queryWrapper = new LambdaQueryWrapper<>();
|
BuriedPointFailRecord existingRecord = findFailRecord(correlationId);
|
||||||
queryWrapper.eq(BuriedPointFailRecord::getCorrelationId, correlationId);
|
|
||||||
BuriedPointFailRecord existingRecord = buriedPointFailRecordMapper.selectOne(queryWrapper);
|
|
||||||
|
|
||||||
if (existingRecord != null) {
|
if (existingRecord != null) {
|
||||||
log.info("[埋点消费者] 发现已有失败记录,将更新: {}", correlationId);
|
updateExistingFailRecord(existingRecord, message, cause);
|
||||||
existingRecord.setExchange(BuriedMessages.EXCHANGE);
|
|
||||||
existingRecord.setRoutingKey(BuriedMessages.ROUTING_KEY);
|
|
||||||
existingRecord.setCause(message.getErrorMessage()+cause);
|
|
||||||
existingRecord.setMessageContent(JsonUtils.toJsonString(message));
|
|
||||||
existingRecord.setRetryCount(message.getRetryCount());
|
|
||||||
existingRecord.setStatus(BuriedPointFailRecord.STATUS_UNPROCESSED);
|
|
||||||
existingRecord.setUpdateTime(new Date());
|
|
||||||
buriedPointFailRecordMapper.updateById(existingRecord);
|
|
||||||
log.info("[埋点消费者] 已更新失败记录, 事件ID: {}", correlationId);
|
|
||||||
} else {
|
} else {
|
||||||
|
createNewFailRecord(message, cause);
|
||||||
|
checkFailRecordsAndAlert();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[埋点消费者] 保存失败记录失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新已存在的失败记录
|
||||||
|
*/
|
||||||
|
private void updateExistingFailRecord(BuriedPointFailRecord record, BuriedMessages message, String cause) {
|
||||||
|
record.setExchange(BuriedMessages.EXCHANGE);
|
||||||
|
record.setRoutingKey(BuriedMessages.ROUTING_KEY);
|
||||||
|
record.setCause(message.getErrorMessage() + cause);
|
||||||
|
record.setMessageContent(JsonUtils.toJsonString(message));
|
||||||
|
record.setRetryCount(message.getRetryCount());
|
||||||
|
record.setStatus(BuriedPointFailRecord.STATUS_UNPROCESSED);
|
||||||
|
record.setUpdateTime(new Date());
|
||||||
|
buriedPointFailRecordMapper.updateById(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建新的失败记录
|
||||||
|
*/
|
||||||
|
private void createNewFailRecord(BuriedMessages message, String cause) {
|
||||||
|
String correlationId = String.valueOf(message.getId());
|
||||||
BuriedPointFailRecord failRecord = new BuriedPointFailRecord();
|
BuriedPointFailRecord failRecord = new BuriedPointFailRecord();
|
||||||
failRecord.setCorrelationId(correlationId);
|
failRecord.setCorrelationId(correlationId);
|
||||||
failRecord.setExchange(BuriedMessages.EXCHANGE);
|
failRecord.setExchange(BuriedMessages.EXCHANGE);
|
||||||
failRecord.setRoutingKey(BuriedMessages.ROUTING_KEY);
|
failRecord.setRoutingKey(BuriedMessages.ROUTING_KEY);
|
||||||
failRecord.setCause(message.getErrorMessage()+cause);
|
failRecord.setCause(message.getErrorMessage() + cause);
|
||||||
failRecord.setMessageContent(JsonUtils.toJsonString(message));
|
failRecord.setMessageContent(JsonUtils.toJsonString(message));
|
||||||
failRecord.setRetryCount(message.getRetryCount());
|
failRecord.setRetryCount(message.getRetryCount());
|
||||||
failRecord.setStatus(BuriedPointFailRecord.STATUS_UNPROCESSED);
|
failRecord.setStatus(BuriedPointFailRecord.STATUS_UNPROCESSED);
|
||||||
failRecord.setCreateTime(new Date());
|
failRecord.setCreateTime(new Date());
|
||||||
failRecord.setUpdateTime(new Date());
|
failRecord.setUpdateTime(new Date());
|
||||||
buriedPointFailRecordMapper.insert(failRecord);
|
buriedPointFailRecordMapper.insert(failRecord);
|
||||||
log.info("[埋点消费者] 已将失败消息保存到失败记录表, 事件ID: {}", message.getId());
|
|
||||||
|
|
||||||
// 查询最近12小时的失败记录数量
|
|
||||||
checkFailRecordsAndAlert();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("[埋点消费者] 保存失败记录失败: {}, 错误: {}", message.getId(), e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -224,85 +251,9 @@ public class BuriedPointConsumer extends AbstractRabbitMQConsumer<BuriedMessages
|
|||||||
*/
|
*/
|
||||||
private void checkFailRecordsAndAlert() {
|
private void checkFailRecordsAndAlert() {
|
||||||
try {
|
try {
|
||||||
Date now = new Date();
|
buriedPointMonitorService.checkFailRecordsAndAlert("埋点处理异常,请检查系统");
|
||||||
Date twelveHoursAgo = new Date(now.getTime() - 12 * 60 * 60 * 1000L);
|
|
||||||
LambdaQueryWrapper<BuriedPointFailRecord> failRecordQuery = new LambdaQueryWrapper<>();
|
|
||||||
failRecordQuery.ge(BuriedPointFailRecord::getCreateTime, twelveHoursAgo)
|
|
||||||
.le(BuriedPointFailRecord::getCreateTime, now)
|
|
||||||
.eq(BuriedPointFailRecord::getStatus, BuriedPointFailRecord.STATUS_UNPROCESSED);
|
|
||||||
|
|
||||||
Long failCountLast12Hours = buriedPointFailRecordMapper.selectCount(failRecordQuery);
|
|
||||||
log.warn("[埋点配置] 最近12小时埋点失败数量: {}", failCountLast12Hours);
|
|
||||||
|
|
||||||
// 如果失败数量过多,记录警告日志
|
|
||||||
if (failCountLast12Hours > 3) {
|
|
||||||
// 查询最近12小时的埋点失败数据,按小时统计
|
|
||||||
List<ChartImageGenerator.MonitoringDataPoint> monitoringData = queryHourlyFailRecordData(twelveHoursAgo, now);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 发送飞书告警消息
|
|
||||||
feiShuAlertClient.sendBuriedPointAlertMessage(larkConfig.getChatId(),
|
|
||||||
monitoringData,
|
|
||||||
failCountLast12Hours.intValue(),
|
|
||||||
"埋点处理异常,请检查系统");
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[埋点配置] 发送飞书告警失败", e);
|
log.error("[埋点消费者] 检查失败记录异常", e);
|
||||||
}
|
|
||||||
log.error("[埋点配置] 警告:最近12小时埋点失败数量过多,请检查系统!失败数量: {}", failCountLast12Hours);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("[埋点配置] 检查失败记录数量异常", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询失败记录数据,按小时统计
|
|
||||||
*/
|
|
||||||
private List<ChartImageGenerator.MonitoringDataPoint> queryHourlyFailRecordData(Date startDate, Date endDate) {
|
|
||||||
List<ChartImageGenerator.MonitoringDataPoint> result = new ArrayList<>();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 只取最近12个小时的数据
|
|
||||||
Calendar calendar = Calendar.getInstance();
|
|
||||||
calendar.setTime(endDate);
|
|
||||||
calendar.add(Calendar.HOUR_OF_DAY, -12);
|
|
||||||
Date twelveHoursAgo = calendar.getTime();
|
|
||||||
|
|
||||||
SimpleDateFormat sdf = new SimpleDateFormat("HH:00");
|
|
||||||
|
|
||||||
// 从12小时前开始,每小时一个数据点
|
|
||||||
for (int i = 0; i < 12; i++) {
|
|
||||||
calendar.setTime(twelveHoursAgo);
|
|
||||||
calendar.add(Calendar.HOUR_OF_DAY, i);
|
|
||||||
Date currentHourStart = calendar.getTime();
|
|
||||||
calendar.add(Calendar.HOUR_OF_DAY, 1);
|
|
||||||
Date nextHourStart = calendar.getTime();
|
|
||||||
|
|
||||||
// 查询处理成功的记录数量
|
|
||||||
LambdaQueryWrapper<BuriedPointFailRecord> successQuery = new LambdaQueryWrapper<>();
|
|
||||||
successQuery.ge(BuriedPointFailRecord::getCreateTime, currentHourStart)
|
|
||||||
.lt(BuriedPointFailRecord::getCreateTime, nextHourStart)
|
|
||||||
.eq(BuriedPointFailRecord::getStatus, BuriedPointFailRecord.STATUS_SUCCESS); // 处理成功
|
|
||||||
Long successCount = buriedPointFailRecordMapper.selectCount(successQuery);
|
|
||||||
|
|
||||||
// 查询处理失败或未处理的记录数量
|
|
||||||
LambdaQueryWrapper<BuriedPointFailRecord> failQuery = new LambdaQueryWrapper<>();
|
|
||||||
failQuery.ge(BuriedPointFailRecord::getCreateTime, currentHourStart)
|
|
||||||
.lt(BuriedPointFailRecord::getCreateTime, nextHourStart)
|
|
||||||
.in(BuriedPointFailRecord::getStatus,
|
|
||||||
Arrays.asList(BuriedPointFailRecord.STATUS_UNPROCESSED, BuriedPointFailRecord.STATUS_FAILED)); // 未处理或处理失败
|
|
||||||
Long failCount = buriedPointFailRecordMapper.selectCount(failQuery);
|
|
||||||
|
|
||||||
// 添加到结果列表,无论是否有数据
|
|
||||||
String hourLabel = sdf.format(currentHourStart);
|
|
||||||
result.add(new ChartImageGenerator.MonitoringDataPoint(hourLabel, successCount.intValue(), failCount.intValue()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("[埋点配置] 查询每小时失败记录数据失败", e);
|
|
||||||
// 返回空列表
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,62 +3,73 @@ package com.tashow.cloud.app.mq.handler;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.tashow.cloud.app.mapper.BuriedPointFailRecordMapper;
|
import com.tashow.cloud.app.mapper.BuriedPointFailRecordMapper;
|
||||||
import com.tashow.cloud.app.model.BuriedPointFailRecord;
|
import com.tashow.cloud.app.model.BuriedPointFailRecord;
|
||||||
|
import com.tashow.cloud.app.service.feishu.BuriedPointMonitorService;
|
||||||
import com.tashow.cloud.mq.handler.FailRecordHandler;
|
import com.tashow.cloud.mq.handler.FailRecordHandler;
|
||||||
import com.tashow.cloud.sdk.feishu.client.FeiShuAlertClient;
|
|
||||||
import com.tashow.cloud.sdk.feishu.config.LarkConfig;
|
|
||||||
import com.tashow.cloud.sdk.feishu.util.ChartImageGenerator;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 埋点失败记录处理器
|
* 埋点失败记录处理器
|
||||||
*
|
|
||||||
* @author tashow
|
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class BuriedPointFailRecordHandler implements FailRecordHandler {
|
public class BuriedPointFailRecordHandler implements FailRecordHandler {
|
||||||
|
|
||||||
|
private final BuriedPointFailRecordMapper buriedPointFailRecordMapper;
|
||||||
|
private final BuriedPointMonitorService buriedPointMonitorService;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private BuriedPointFailRecordMapper buriedPointFailRecordMapper;
|
|
||||||
@Autowired
|
|
||||||
FeiShuAlertClient feiShuAlertClient;
|
|
||||||
@Autowired
|
|
||||||
LarkConfig larkConfig;
|
|
||||||
/**
|
/**
|
||||||
* 保存消息发送失败记录
|
* 保存消息发送失败记录
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void saveFailRecord(String correlationId, String exchange, String routingKey, String cause, String messageContent) {
|
public void saveFailRecord(String correlationId, String exchange, String routingKey, String cause, String messageContent) {
|
||||||
try {
|
try {
|
||||||
log.info("[埋点处理器] 保存发送失败记录: correlationId={}", correlationId);
|
|
||||||
// 先查询是否已存在记录
|
// 先查询是否已存在记录
|
||||||
|
BuriedPointFailRecord existingRecord = findExistingRecord(correlationId);
|
||||||
|
|
||||||
|
if (existingRecord != null) {
|
||||||
|
updateExistingRecord(existingRecord, exchange, routingKey, cause, messageContent);
|
||||||
|
} else {
|
||||||
|
createNewFailRecord(correlationId, exchange, routingKey, cause, messageContent);
|
||||||
|
checkAlertThreshold(cause);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[埋点处理器] 保存失败记录异常", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找已存在的失败记录
|
||||||
|
*/
|
||||||
|
private BuriedPointFailRecord findExistingRecord(String correlationId) {
|
||||||
LambdaQueryWrapper<BuriedPointFailRecord> queryWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<BuriedPointFailRecord> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
queryWrapper.eq(BuriedPointFailRecord::getCorrelationId, correlationId);
|
queryWrapper.eq(BuriedPointFailRecord::getCorrelationId, correlationId);
|
||||||
BuriedPointFailRecord existingRecord = buriedPointFailRecordMapper.selectOne(queryWrapper);
|
return buriedPointFailRecordMapper.selectOne(queryWrapper);
|
||||||
if (existingRecord != null) {
|
}
|
||||||
log.info("[埋点处理器] 发现已有失败记录,将更新: {}", correlationId);
|
|
||||||
existingRecord.setExchange(exchange);
|
/**
|
||||||
existingRecord.setRoutingKey(routingKey);
|
* 更新已存在的失败记录
|
||||||
existingRecord.setCause(cause);
|
*/
|
||||||
existingRecord.setMessageContent(messageContent);
|
private void updateExistingRecord(BuriedPointFailRecord record, String exchange, String routingKey,
|
||||||
existingRecord.setStatus(BuriedPointFailRecord.STATUS_UNPROCESSED);
|
String cause, String messageContent) {
|
||||||
existingRecord.setUpdateTime(new Date());
|
record.setExchange(exchange);
|
||||||
buriedPointFailRecordMapper.updateById(existingRecord);
|
record.setRoutingKey(routingKey);
|
||||||
log.info("[埋点处理器] 发送失败记录已更新: correlationId={}", correlationId);
|
record.setCause(cause);
|
||||||
} else {
|
record.setMessageContent(messageContent);
|
||||||
|
record.setStatus(BuriedPointFailRecord.STATUS_UNPROCESSED);
|
||||||
|
record.setUpdateTime(new Date());
|
||||||
|
buriedPointFailRecordMapper.updateById(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建新的失败记录
|
||||||
|
*/
|
||||||
|
private void createNewFailRecord(String correlationId, String exchange, String routingKey,
|
||||||
|
String cause, String messageContent) {
|
||||||
BuriedPointFailRecord failRecord = new BuriedPointFailRecord();
|
BuriedPointFailRecord failRecord = new BuriedPointFailRecord();
|
||||||
failRecord.setCorrelationId(correlationId);
|
failRecord.setCorrelationId(correlationId);
|
||||||
failRecord.setExchange(exchange);
|
failRecord.setExchange(exchange);
|
||||||
@@ -70,12 +81,6 @@ public class BuriedPointFailRecordHandler implements FailRecordHandler {
|
|||||||
failRecord.setCreateTime(new Date());
|
failRecord.setCreateTime(new Date());
|
||||||
failRecord.setUpdateTime(new Date());
|
failRecord.setUpdateTime(new Date());
|
||||||
buriedPointFailRecordMapper.insert(failRecord);
|
buriedPointFailRecordMapper.insert(failRecord);
|
||||||
log.info("[埋点处理器] 发送失败记录已保存: correlationId={}", correlationId);
|
|
||||||
checkAlertThreshold(cause);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("[埋点处理器] 保存发送失败记录异常", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -83,74 +88,6 @@ public class BuriedPointFailRecordHandler implements FailRecordHandler {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean checkAlertThreshold(String cause) {
|
public boolean checkAlertThreshold(String cause) {
|
||||||
try {
|
return buriedPointMonitorService.checkFailRecordsAndAlert(cause);
|
||||||
Date now = new Date();
|
|
||||||
Date twelveHoursAgo = new Date(now.getTime() - 12 * 60 * 60 * 1000L);
|
|
||||||
LambdaQueryWrapper<BuriedPointFailRecord> failRecordQuery = new LambdaQueryWrapper<>();
|
|
||||||
failRecordQuery.ge(BuriedPointFailRecord::getCreateTime, twelveHoursAgo).le(BuriedPointFailRecord::getCreateTime, now).eq(BuriedPointFailRecord::getStatus, BuriedPointFailRecord.STATUS_UNPROCESSED);
|
|
||||||
Long failCountLast12Hours = buriedPointFailRecordMapper.selectCount(failRecordQuery);
|
|
||||||
// 如果失败数量过多,记录警告日志
|
|
||||||
if (failCountLast12Hours > 3) {
|
|
||||||
List<ChartImageGenerator.MonitoringDataPoint> monitoringData = queryHourlyFailRecordData(twelveHoursAgo, now);
|
|
||||||
try {
|
|
||||||
// 发送飞书告警消息
|
|
||||||
feiShuAlertClient.sendBuriedPointAlertMessage(larkConfig.getChatId(), monitoringData, failCountLast12Hours.intValue(), cause);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("[埋点处理器] 发送飞书告警失败", e);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("[埋点处理器] 检查告警阈值异常", e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询失败记录数据,按小时统计
|
|
||||||
* 仅查询最近12个小时的数据
|
|
||||||
*/
|
|
||||||
private List<ChartImageGenerator.MonitoringDataPoint> queryHourlyFailRecordData(Date startDate, Date endDate) {
|
|
||||||
List<ChartImageGenerator.MonitoringDataPoint> result = new ArrayList<>();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 只取最近12个小时的数据
|
|
||||||
Calendar calendar = Calendar.getInstance();
|
|
||||||
calendar.setTime(endDate);
|
|
||||||
calendar.add(Calendar.HOUR_OF_DAY, -12);
|
|
||||||
Date twelveHoursAgo = calendar.getTime();
|
|
||||||
|
|
||||||
SimpleDateFormat sdf = new SimpleDateFormat("HH:00");
|
|
||||||
|
|
||||||
// 从12小时前开始,每小时一个数据点
|
|
||||||
for (int i = 0; i < 12; i++) {
|
|
||||||
calendar.setTime(twelveHoursAgo);
|
|
||||||
calendar.add(Calendar.HOUR_OF_DAY, i);
|
|
||||||
Date currentHourStart = calendar.getTime();
|
|
||||||
calendar.add(Calendar.HOUR_OF_DAY, 1);
|
|
||||||
Date nextHourStart = calendar.getTime();
|
|
||||||
|
|
||||||
// 查询处理成功的记录数量
|
|
||||||
LambdaQueryWrapper<BuriedPointFailRecord> successQuery = new LambdaQueryWrapper<>();
|
|
||||||
successQuery.ge(BuriedPointFailRecord::getCreateTime, currentHourStart).lt(BuriedPointFailRecord::getCreateTime, nextHourStart).eq(BuriedPointFailRecord::getStatus, BuriedPointFailRecord.STATUS_SUCCESS); // 处理成功
|
|
||||||
Long successCount = buriedPointFailRecordMapper.selectCount(successQuery);
|
|
||||||
|
|
||||||
// 查询处理失败或未处理的记录数量
|
|
||||||
LambdaQueryWrapper<BuriedPointFailRecord> failQuery = new LambdaQueryWrapper<>();
|
|
||||||
failQuery.ge(BuriedPointFailRecord::getCreateTime, currentHourStart).lt(BuriedPointFailRecord::getCreateTime, nextHourStart).in(BuriedPointFailRecord::getStatus, Arrays.asList(BuriedPointFailRecord.STATUS_UNPROCESSED, BuriedPointFailRecord.STATUS_FAILED)); // 未处理或处理失败
|
|
||||||
Long failCount = buriedPointFailRecordMapper.selectCount(failQuery);
|
|
||||||
|
|
||||||
// 添加到结果列表,无论是否有数据
|
|
||||||
String hourLabel = sdf.format(currentHourStart);
|
|
||||||
result.add(new ChartImageGenerator.MonitoringDataPoint(hourLabel, successCount.intValue(), failCount.intValue()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("[埋点处理器] 查询每小时失败记录数据失败", e);
|
|
||||||
// 返回空列表
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
package com.tashow.cloud.app.service.feishu;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.tashow.cloud.app.mapper.BuriedPointFailRecordMapper;
|
||||||
|
import com.tashow.cloud.app.model.BuriedPointFailRecord;
|
||||||
|
import com.tashow.cloud.sdk.feishu.client.FeiShuAlertClient;
|
||||||
|
import com.tashow.cloud.sdk.feishu.config.LarkConfig;
|
||||||
|
import com.tashow.cloud.sdk.feishu.util.ChartImageGenerator;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 埋点监控服务
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class BuriedPointMonitorService {
|
||||||
|
|
||||||
|
private static final int ALERT_THRESHOLD = 3;
|
||||||
|
private static final int MONITORING_HOURS = 12;
|
||||||
|
|
||||||
|
private final BuriedPointFailRecordMapper buriedPointFailRecordMapper;
|
||||||
|
private final FeiShuAlertClient feiShuAlertClient;
|
||||||
|
private final FeiShuCardDataService feiShuCardDataService;
|
||||||
|
private final LarkConfig larkConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查失败记录并发送告警
|
||||||
|
*/
|
||||||
|
public boolean checkFailRecordsAndAlert(String cause) {
|
||||||
|
try {
|
||||||
|
Date now = new Date();
|
||||||
|
Date hoursAgo = getDateHoursAgo(now, MONITORING_HOURS);
|
||||||
|
|
||||||
|
Long failCount = getUnprocessedFailCount(hoursAgo, now);
|
||||||
|
|
||||||
|
if (failCount > ALERT_THRESHOLD) {
|
||||||
|
sendAlertMessage(failCount.intValue(), hoursAgo, now, cause);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[埋点监控] 检查失败记录异常", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取未处理的失败记录数量
|
||||||
|
*/
|
||||||
|
public Long getUnprocessedFailCount(Date startDate, Date endDate) {
|
||||||
|
LambdaQueryWrapper<BuriedPointFailRecord> query = new LambdaQueryWrapper<>();
|
||||||
|
query.ge(BuriedPointFailRecord::getCreateTime, startDate)
|
||||||
|
.le(BuriedPointFailRecord::getCreateTime, endDate)
|
||||||
|
.eq(BuriedPointFailRecord::getStatus, BuriedPointFailRecord.STATUS_UNPROCESSED);
|
||||||
|
return buriedPointFailRecordMapper.selectCount(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送告警消息
|
||||||
|
*/
|
||||||
|
private void sendAlertMessage(int failCount, Date startDate, Date endDate, String errorMessage) {
|
||||||
|
try {
|
||||||
|
List<ChartImageGenerator.MonitoringDataPoint> monitoringData =
|
||||||
|
queryHourlyFailRecordData(startDate, endDate);
|
||||||
|
|
||||||
|
HashMap<String, Object> templateData = new HashMap<>();
|
||||||
|
String chatId = larkConfig.getChatId();
|
||||||
|
|
||||||
|
String imageKey = feiShuAlertClient.uploadImage(monitoringData, errorMessage);
|
||||||
|
|
||||||
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
templateData.put("alert_title", "埋点数据异常告警");
|
||||||
|
templateData.put("image_key", imageKey);
|
||||||
|
templateData.put("current_time", sdf.format(new Date()));
|
||||||
|
templateData.put("fail_count", failCount);
|
||||||
|
|
||||||
|
String messageId = feiShuAlertClient.sendCardMessage(
|
||||||
|
chatId,
|
||||||
|
larkConfig.getExceptionCards(),
|
||||||
|
templateData
|
||||||
|
);
|
||||||
|
feiShuCardDataService.saveCardData(messageId, templateData);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[埋点监控] 发送告警失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询按小时统计的失败记录数据
|
||||||
|
*/
|
||||||
|
public List<ChartImageGenerator.MonitoringDataPoint> queryHourlyFailRecordData(Date startDate, Date endDate) {
|
||||||
|
List<ChartImageGenerator.MonitoringDataPoint> result = new ArrayList<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Date limitedStartDate = getDateHoursAgo(endDate, MONITORING_HOURS);
|
||||||
|
Date actualStartDate = startDate.after(limitedStartDate) ? startDate : limitedStartDate;
|
||||||
|
|
||||||
|
SimpleDateFormat sdf = new SimpleDateFormat("HH:00");
|
||||||
|
Calendar calendar = Calendar.getInstance();
|
||||||
|
|
||||||
|
long hoursDiff = (endDate.getTime() - actualStartDate.getTime()) / (60 * 60 * 1000) + 1;
|
||||||
|
int hours = (int) Math.min(hoursDiff, MONITORING_HOURS);
|
||||||
|
|
||||||
|
for (int i = 0; i < hours; i++) {
|
||||||
|
calendar.setTime(actualStartDate);
|
||||||
|
calendar.add(Calendar.HOUR_OF_DAY, i);
|
||||||
|
Date currentHourStart = calendar.getTime();
|
||||||
|
calendar.add(Calendar.HOUR_OF_DAY, 1);
|
||||||
|
Date nextHourStart = calendar.getTime();
|
||||||
|
|
||||||
|
int successCount = getHourlyRecordCount(currentHourStart, nextHourStart, BuriedPointFailRecord.STATUS_SUCCESS);
|
||||||
|
int failCount = getHourlyFailedCount(currentHourStart, nextHourStart);
|
||||||
|
|
||||||
|
result.add(new ChartImageGenerator.MonitoringDataPoint(
|
||||||
|
sdf.format(currentHourStart),
|
||||||
|
successCount,
|
||||||
|
failCount
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[埋点监控] 查询小时数据失败", e);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定状态的记录数量
|
||||||
|
*/
|
||||||
|
private int getHourlyRecordCount(Date startHour, Date endHour, int status) {
|
||||||
|
LambdaQueryWrapper<BuriedPointFailRecord> query = new LambdaQueryWrapper<>();
|
||||||
|
query.ge(BuriedPointFailRecord::getCreateTime, startHour)
|
||||||
|
.lt(BuriedPointFailRecord::getCreateTime, endHour)
|
||||||
|
.eq(BuriedPointFailRecord::getStatus, status);
|
||||||
|
return buriedPointFailRecordMapper.selectCount(query).intValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取失败记录数量
|
||||||
|
*/
|
||||||
|
private int getHourlyFailedCount(Date startHour, Date endHour) {
|
||||||
|
LambdaQueryWrapper<BuriedPointFailRecord> query = new LambdaQueryWrapper<>();
|
||||||
|
query.ge(BuriedPointFailRecord::getCreateTime, startHour)
|
||||||
|
.lt(BuriedPointFailRecord::getCreateTime, endHour)
|
||||||
|
.in(BuriedPointFailRecord::getStatus,
|
||||||
|
Arrays.asList(BuriedPointFailRecord.STATUS_UNPROCESSED, BuriedPointFailRecord.STATUS_FAILED));
|
||||||
|
return buriedPointFailRecordMapper.selectCount(query).intValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定时间前N小时的时间
|
||||||
|
*/
|
||||||
|
private Date getDateHoursAgo(Date date, int hours) {
|
||||||
|
Calendar calendar = Calendar.getInstance();
|
||||||
|
calendar.setTime(date);
|
||||||
|
calendar.add(Calendar.HOUR_OF_DAY, -hours);
|
||||||
|
return calendar.getTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ package com.tashow.cloud.app.service.feishu;
|
|||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.tashow.cloud.sdk.feishu.client.FeiShuAlertClient;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -15,12 +14,14 @@ import java.util.concurrent.TimeUnit;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 飞书卡片数据处理服务
|
* 飞书卡片数据处理服务
|
||||||
* 负责卡片数据的存储和获取
|
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class FeiShuCardDataService implements FeiShuAlertClient.CardDataHandler {
|
public class FeiShuCardDataService {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(FeiShuCardDataService.class);
|
||||||
|
private static final String REDIS_KEY_PREFIX = "feishu:card:";
|
||||||
|
private static final int CARD_EXPIRATION_DAYS = 30;
|
||||||
|
|
||||||
private final Logger log = LoggerFactory.getLogger(FeiShuCardDataService.class);
|
|
||||||
private final StringRedisTemplate stringRedisTemplate;
|
private final StringRedisTemplate stringRedisTemplate;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
@@ -32,34 +33,41 @@ public class FeiShuCardDataService implements FeiShuAlertClient.CardDataHandler
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存卡片数据到Redis
|
* 保存卡片数据到Redis
|
||||||
* @param messageId 消息ID
|
|
||||||
* @param data 卡片数据
|
|
||||||
*/
|
*/
|
||||||
@Override
|
public boolean saveCardData(String messageId, Map<String, Object> data) {
|
||||||
public void saveCardData(String messageId, Map<String, Object> data) {
|
if (messageId == null || data == null) return false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String jsonData = objectMapper.writeValueAsString(data);
|
String jsonData = objectMapper.writeValueAsString(data);
|
||||||
stringRedisTemplate.opsForValue().set(messageId, jsonData, 30, TimeUnit.DAYS);
|
stringRedisTemplate.opsForValue().set(
|
||||||
log.debug("卡片数据已保存到Redis, messageId: {}", messageId);
|
REDIS_KEY_PREFIX + messageId,
|
||||||
|
jsonData,
|
||||||
|
CARD_EXPIRATION_DAYS,
|
||||||
|
TimeUnit.DAYS
|
||||||
|
);
|
||||||
|
return true;
|
||||||
} catch (JsonProcessingException e) {
|
} catch (JsonProcessingException e) {
|
||||||
log.error("保存卡片数据到Redis失败", e);
|
log.error("保存卡片数据失败: {}", e.getMessage());
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从Redis获取卡片数据
|
* 从Redis获取卡片数据
|
||||||
* @param messageId 消息ID
|
|
||||||
* @return 卡片数据
|
|
||||||
*/
|
*/
|
||||||
@Override
|
|
||||||
public Map<String, Object> getCardData(String messageId) {
|
public Map<String, Object> getCardData(String messageId) {
|
||||||
try {
|
try {
|
||||||
String jsonData = stringRedisTemplate.opsForValue().get(messageId);
|
String redisKey = REDIS_KEY_PREFIX + messageId;
|
||||||
return objectMapper.readValue(jsonData, Map.class);
|
String jsonData = stringRedisTemplate.opsForValue().get(redisKey);
|
||||||
|
|
||||||
|
if (jsonData == null) return new HashMap<>();
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> result = objectMapper.readValue(jsonData, Map.class);
|
||||||
|
return result;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
log.error("获取卡片数据失败: {}", e.getMessage());
|
||||||
|
return new HashMap<>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -44,25 +44,19 @@ public class BuriedPointFailRecordService implements MessageRetryService<BuriedP
|
|||||||
try {
|
try {
|
||||||
Long id = Long.valueOf(recordId);
|
Long id = Long.valueOf(recordId);
|
||||||
BuriedPointFailRecord record = buriedPointFailRecordMapper.selectById(id);
|
BuriedPointFailRecord record = buriedPointFailRecordMapper.selectById(id);
|
||||||
if (record == null) {
|
|
||||||
log.warn("[埋点重试] 未找到失败记录: {}", id);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
BuriedMessages message = JsonUtils.parseObject(record.getMessageContent(), BuriedMessages.class);
|
BuriedMessages message = JsonUtils.parseObject(record.getMessageContent(), BuriedMessages.class);
|
||||||
if (message == null) {
|
if (message == null) {
|
||||||
log.error("[埋点重试] 消息内容解析失败: {}", record.getCorrelationId());
|
|
||||||
updateStatus(record, BuriedPointFailRecord.STATUS_FAILED);
|
updateStatus(record, BuriedPointFailRecord.STATUS_FAILED);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
log.info("[埋点重试] 准备重新发送消息: {}", record.getCorrelationId());
|
|
||||||
buriedPointProducer.asyncSendMessage(message, record.getCorrelationId());
|
buriedPointProducer.asyncSendMessage(message, record.getCorrelationId());
|
||||||
record.setStatus(BuriedPointFailRecord.STATUS_SUCCESS);
|
updateStatus(record, BuriedPointFailRecord.STATUS_SUCCESS);
|
||||||
record.setUpdateTime(new Date());
|
|
||||||
buriedPointFailRecordMapper.updateById(record);
|
|
||||||
log.info("[埋点重试] 重试成功,状态已更新为成功: {}", record.getCorrelationId());
|
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[埋点重试] 重试失败消息异常: {}", recordId, e);
|
log.error("[埋点重试] 重试失败", e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,10 +69,9 @@ public class BuriedPointFailRecordService implements MessageRetryService<BuriedP
|
|||||||
try {
|
try {
|
||||||
record.setStatus(status);
|
record.setStatus(status);
|
||||||
record.setUpdateTime(new Date());
|
record.setUpdateTime(new Date());
|
||||||
int result = buriedPointFailRecordMapper.updateById(record);
|
return buriedPointFailRecordMapper.updateById(record) > 0;
|
||||||
return result > 0;
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[埋点重试] 更新状态失败: {}", record.getCorrelationId(), e);
|
log.error("[埋点重试] 更新状态失败", e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import java.text.SimpleDateFormat;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import com.tashow.cloud.sdk.feishu.config.LarkConfig;
|
import com.tashow.cloud.sdk.feishu.config.LarkConfig;
|
||||||
import com.tashow.cloud.sdk.feishu.util.LarkClientUtil;
|
import com.tashow.cloud.sdk.feishu.util.LarkClientUtil;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -25,32 +24,15 @@ public class FeiShuAlertClient {
|
|||||||
private final Client client;
|
private final Client client;
|
||||||
private final LarkConfig larkConfig;
|
private final LarkConfig larkConfig;
|
||||||
private final ChartImageGenerator chartImageGenerator;
|
private final ChartImageGenerator chartImageGenerator;
|
||||||
public interface CardDataHandler {
|
|
||||||
/**
|
|
||||||
* 保存卡片数据
|
|
||||||
* @param messageId 消息ID
|
|
||||||
* @param data 卡片数据
|
|
||||||
*/
|
|
||||||
void saveCardData(String messageId, Map<String, Object> data);
|
|
||||||
/**
|
|
||||||
* 获取卡片数据
|
|
||||||
* @param messageId 消息ID
|
|
||||||
* @return 卡片数据
|
|
||||||
*/
|
|
||||||
Map<String, Object> getCardData(String messageId);
|
|
||||||
}
|
|
||||||
private CardDataHandler cardDataHandler;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public FeiShuAlertClient(LarkClientUtil larkClientUtil, LarkConfig larkConfig,
|
public FeiShuAlertClient(LarkClientUtil larkClientUtil, LarkConfig larkConfig,
|
||||||
ChartImageGenerator chartImageGenerator, ObjectMapper objectMapper) {
|
ChartImageGenerator chartImageGenerator) {
|
||||||
this.client = larkClientUtil.getLarkClient();
|
this.client = larkClientUtil.getLarkClient();
|
||||||
this.larkConfig = larkConfig;
|
this.larkConfig = larkConfig;
|
||||||
this.chartImageGenerator = chartImageGenerator;
|
this.chartImageGenerator = chartImageGenerator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建报警群并拉人入群
|
* 创建报警群并拉人入群
|
||||||
*
|
*
|
||||||
@@ -74,18 +56,6 @@ public class FeiShuAlertClient {
|
|||||||
return resp.getData().getChatId();
|
return resp.getData().getChatId();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送埋点报警消息
|
|
||||||
*
|
|
||||||
* @param chatId 会话ID
|
|
||||||
* @param buriedPointData 埋点数据
|
|
||||||
* @param failCount 失败数量
|
|
||||||
* @throws Exception 异常信息
|
|
||||||
*/
|
|
||||||
public void sendBuriedPointAlertMessage(String chatId, List<ChartImageGenerator.MonitoringDataPoint> buriedPointData, int failCount) throws Exception {
|
|
||||||
sendBuriedPointAlertMessage(chatId, buriedPointData, failCount, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送带错误信息的埋点报警消息
|
* 发送带错误信息的埋点报警消息
|
||||||
*
|
*
|
||||||
@@ -93,9 +63,10 @@ public class FeiShuAlertClient {
|
|||||||
* @param buriedPointData 埋点数据
|
* @param buriedPointData 埋点数据
|
||||||
* @param failCount 失败数量
|
* @param failCount 失败数量
|
||||||
* @param errorMessage 错误信息
|
* @param errorMessage 错误信息
|
||||||
|
* @return 发送的消息ID
|
||||||
* @throws Exception 异常信息
|
* @throws Exception 异常信息
|
||||||
*/
|
*/
|
||||||
public void sendBuriedPointAlertMessage(String chatId, List<ChartImageGenerator.MonitoringDataPoint> buriedPointData, int failCount, String errorMessage) throws Exception {
|
public String sendBuriedPointAlertMessage(String chatId, List<ChartImageGenerator.MonitoringDataPoint> buriedPointData, int failCount, String errorMessage) throws Exception {
|
||||||
HashMap<String, Object> templateData = new HashMap<>();
|
HashMap<String, Object> templateData = new HashMap<>();
|
||||||
String imageKey = uploadImage(buriedPointData, errorMessage);
|
String imageKey = uploadImage(buriedPointData, errorMessage);
|
||||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
@@ -104,7 +75,7 @@ public class FeiShuAlertClient {
|
|||||||
templateData.put("image_key", imageKey);
|
templateData.put("image_key", imageKey);
|
||||||
templateData.put("current_time", currentTime);
|
templateData.put("current_time", currentTime);
|
||||||
templateData.put("fail_count", failCount);
|
templateData.put("fail_count", failCount);
|
||||||
sendCardMessage(chatId, "AAqdpjayeOVp2", templateData);
|
return sendCardMessage(chatId, "AAqdpjayeOVp2", templateData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -113,11 +84,13 @@ public class FeiShuAlertClient {
|
|||||||
* @param buriedPointData 埋点数据
|
* @param buriedPointData 埋点数据
|
||||||
* @param failCount 失败数量
|
* @param failCount 失败数量
|
||||||
* @param errorMessage 错误信息
|
* @param errorMessage 错误信息
|
||||||
|
* @return 创建的群ID和消息ID,格式为 "chatId:messageId"
|
||||||
* @throws Exception 异常信息
|
* @throws Exception 异常信息
|
||||||
*/
|
*/
|
||||||
public void sendBuriedPointAlertMessage(List<ChartImageGenerator.MonitoringDataPoint> buriedPointData, int failCount, String errorMessage) throws Exception {
|
public String sendBuriedPointAlertMessage(List<ChartImageGenerator.MonitoringDataPoint> buriedPointData, int failCount, String errorMessage) throws Exception {
|
||||||
String chatId = createAlertChat();
|
String chatId = createAlertChat();
|
||||||
sendBuriedPointAlertMessage(chatId, buriedPointData, failCount, errorMessage);
|
String messageId = sendBuriedPointAlertMessage(chatId, buriedPointData, failCount, errorMessage);
|
||||||
|
return chatId + ":" + messageId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -214,47 +187,10 @@ public class FeiShuAlertClient {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 构建埋点异常卡片
|
|
||||||
*
|
|
||||||
* @param buttonName 按钮名称
|
|
||||||
* @param buriedPointData 埋点数据
|
|
||||||
* @param failCount 失败数量
|
|
||||||
* @return 卡片JSON
|
|
||||||
* @throws Exception 异常信息
|
|
||||||
*/
|
|
||||||
private String buildBuriedPointCard(String buttonName, List<ChartImageGenerator.MonitoringDataPoint> buriedPointData, int failCount) throws Exception {
|
|
||||||
return buildBuriedPointCard(buttonName, buriedPointData, failCount, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建埋点异常卡片(带错误信息)
|
* 发送卡片消息
|
||||||
*
|
|
||||||
* @param buttonName 按钮名称
|
|
||||||
* @param buriedPointData 埋点数据
|
|
||||||
* @param failCount 失败数量
|
|
||||||
* @param errorMessage 错误信息
|
|
||||||
* @return 卡片JSON
|
|
||||||
* @throws Exception 异常信息
|
|
||||||
*/
|
|
||||||
private String buildBuriedPointCard(String buttonName, List<ChartImageGenerator.MonitoringDataPoint> buriedPointData, int failCount, String errorMessage) throws Exception {
|
|
||||||
String imageKey = uploadImage(buriedPointData, errorMessage);
|
|
||||||
// 获取当前时间
|
|
||||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
|
||||||
String currentTime = sdf.format(new Date());
|
|
||||||
|
|
||||||
HashMap<String, Object> templateData = new HashMap<>();
|
|
||||||
templateData.put("alert_title", "埋点数据异常告警");
|
|
||||||
templateData.put("image_key", imageKey);
|
|
||||||
templateData.put("current_time", currentTime);
|
|
||||||
templateData.put("fail_count", failCount);
|
|
||||||
templateData.put("button_name", buttonName);
|
|
||||||
|
|
||||||
return buildCardWithData("AAqdpjayeOVp2", templateData);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送卡片消息并保存数据
|
|
||||||
*
|
*
|
||||||
* @param chatId 会话ID
|
* @param chatId 会话ID
|
||||||
* @param templateId 卡片模板ID
|
* @param templateId 卡片模板ID
|
||||||
@@ -264,11 +200,6 @@ public class FeiShuAlertClient {
|
|||||||
*/
|
*/
|
||||||
public String sendCardMessage(String chatId, String templateId, Map<String, Object> templateData) throws Exception {
|
public String sendCardMessage(String chatId, String templateId, Map<String, Object> templateData) throws Exception {
|
||||||
String cardContent = buildCardWithData(templateId, templateData);
|
String cardContent = buildCardWithData(templateId, templateData);
|
||||||
String messageId = sendMessage(chatId, "interactive", cardContent);
|
return sendMessage(chatId, "interactive", cardContent);
|
||||||
if (cardDataHandler != null && messageId != null) {
|
|
||||||
cardDataHandler.saveCardData(messageId, templateData);
|
|
||||||
}
|
|
||||||
|
|
||||||
return messageId;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
package com.tashow.cloud.sdk.feishu.client;
|
package com.tashow.cloud.sdk.feishu.client;
|
||||||
import com.lark.oapi.Client;
|
import com.lark.oapi.Client;
|
||||||
import com.lark.oapi.core.utils.Jsons;
|
|
||||||
import com.lark.oapi.service.im.v1.model.*;
|
import com.lark.oapi.service.im.v1.model.*;
|
||||||
import com.tashow.cloud.sdk.feishu.config.LarkConfig;
|
|
||||||
import com.tashow.cloud.sdk.feishu.util.LarkClientUtil;
|
import com.tashow.cloud.sdk.feishu.util.LarkClientUtil;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.io.File;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 飞书普通消息客户端
|
* 飞书普通消息客户端
|
||||||
@@ -18,92 +15,73 @@ import java.util.*;
|
|||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class FeiShuMessageClient {
|
public class FeiShuMessageClient {
|
||||||
|
private final Logger log = LoggerFactory.getLogger(FeiShuMessageClient.class);
|
||||||
private final Client client;
|
private final Client client;
|
||||||
private final LarkConfig larkConfig;
|
|
||||||
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public FeiShuMessageClient(LarkClientUtil larkClientUtil, LarkConfig larkConfig) {
|
public FeiShuMessageClient(LarkClientUtil larkClientUtil) {
|
||||||
this.client = larkClientUtil.getLarkClient();
|
this.client = larkClientUtil.getLarkClient();
|
||||||
this.larkConfig = larkConfig;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送文本消息
|
* 发送通用消息
|
||||||
* @param chatId 会话ID
|
* @param chatId 会话ID
|
||||||
* @param text 消息文本
|
* @param msgType 消息类型
|
||||||
* @return 发送结果
|
* @param content 消息内容(不同消息类型有不同的格式要求)
|
||||||
|
* @return 发送结果,包含消息ID和是否成功
|
||||||
* @throws Exception 异常信息
|
* @throws Exception 异常信息
|
||||||
*/
|
*/
|
||||||
public boolean sendTextMessage(String chatId, String text) throws Exception {
|
public Map<String, Object> sendMessage(String chatId, String msgType, String content) throws Exception {
|
||||||
Map<String, String> content = new HashMap<>();
|
|
||||||
content.put("text", text);
|
|
||||||
CreateMessageReq req = CreateMessageReq.newBuilder()
|
CreateMessageReq req = CreateMessageReq.newBuilder()
|
||||||
.receiveIdType("chat_id")
|
.receiveIdType("chat_id")
|
||||||
.createMessageReqBody(CreateMessageReqBody.newBuilder()
|
.createMessageReqBody(CreateMessageReqBody.newBuilder()
|
||||||
.receiveId(chatId)
|
.receiveId(chatId)
|
||||||
.msgType("text")
|
.msgType(msgType)
|
||||||
.content(Jsons.DEFAULT.toJson(content))
|
.content(content)
|
||||||
.build())
|
|
||||||
.build();
|
|
||||||
CreateMessageResp resp = client.im().message().create(req);
|
|
||||||
if (!resp.success()) {
|
|
||||||
System.out.println("发送失败原因: " + resp.getMsg() + ", 错误码: " + resp.getCode());
|
|
||||||
}
|
|
||||||
return resp.success();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送富文本消息
|
|
||||||
* @param chatId 会话ID()
|
|
||||||
* @param title 标题
|
|
||||||
* @param content 内容
|
|
||||||
* @return 发送结果
|
|
||||||
* @throws Exception 异常信息
|
|
||||||
*/
|
|
||||||
public boolean sendPostMessage(String chatId, String title, String content) throws Exception {
|
|
||||||
// 正确的富文本消息格式
|
|
||||||
String postJson = String.format("{\"zh_cn\":{\"title\":\"%s\",\"content\":[[{\"tag\":\"text\",\"text\":\"%s\"}]]}}",
|
|
||||||
title, content);
|
|
||||||
CreateMessageReq req = CreateMessageReq.newBuilder()
|
|
||||||
.receiveIdType("chat_id")
|
|
||||||
.createMessageReqBody(CreateMessageReqBody.newBuilder()
|
|
||||||
.receiveId(chatId)
|
|
||||||
.msgType("post")
|
|
||||||
.content(postJson)
|
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
CreateMessageResp resp = client.im().message().create(req);
|
CreateMessageResp resp = client.im().message().create(req);
|
||||||
if (!resp.success()) {
|
Map<String, Object> result = new HashMap<>();
|
||||||
System.out.println("发送失败原因: " + resp.getMsg() + ", 错误码: " + resp.getCode());
|
result.put("success", resp.success());
|
||||||
|
|
||||||
|
if (resp.success()) {
|
||||||
|
result.put("messageId", resp.getData().getMessageId());
|
||||||
|
} else {
|
||||||
|
result.put("errorCode", resp.getCode());
|
||||||
|
result.put("errorMessage", resp.getMsg());
|
||||||
|
result.put("requestId", resp.getRequestId());
|
||||||
|
log.error("发送消息失败: 类型={}, 错误码={}, 错误信息={}", msgType, resp.getCode(), resp.getMsg());
|
||||||
}
|
}
|
||||||
return resp.success();
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取会话历史消息
|
* 获取会话历史消息
|
||||||
* @param chatId 会话ID
|
* @param chatId 会话ID
|
||||||
|
* @return 消息列表
|
||||||
* @throws Exception 异常信息
|
* @throws Exception 异常信息
|
||||||
*/
|
*/
|
||||||
public void listChatHistory(String chatId) throws Exception {
|
public List<Message> listChatHistory(String chatId) throws Exception {
|
||||||
ListMessageReq req = ListMessageReq.newBuilder().containerIdType("chat").containerId(chatId).build();
|
ListMessageReq req = ListMessageReq.newBuilder()
|
||||||
|
.containerIdType("chat")
|
||||||
|
.containerId(chatId)
|
||||||
|
.build();
|
||||||
|
|
||||||
ListMessageResp resp = client.im().message().list(req);
|
ListMessageResp resp = client.im().message().list(req);
|
||||||
|
|
||||||
if (!resp.success()) {
|
if (!resp.success()) {
|
||||||
throw new Exception(String.format("client.im.message.list failed, code: %d, msg: %s, logId: %s", resp.getCode(), resp.getMsg(), resp.getRequestId()));
|
throw new Exception(String.format("获取消息历史失败,错误码: %d, 错误信息: %s, 请求ID: %s",
|
||||||
|
resp.getCode(), resp.getMsg(), resp.getRequestId()));
|
||||||
}
|
}
|
||||||
File file = new File("./src/main/java/com/larksuite/oapi/quick_start/robot/chat_history.txt");
|
|
||||||
FileWriter writer = new FileWriter(file);
|
return Arrays.asList(resp.getData().getItems());
|
||||||
for (Message item : resp.getData().getItems()) {
|
|
||||||
String senderId = item.getSender().getId();
|
|
||||||
String content = item.getBody().getContent();
|
|
||||||
String createTime = item.getCreateTime();
|
|
||||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
|
||||||
createTime = sdf.format(new Date(Long.parseLong(createTime)));
|
|
||||||
writer.write(String.format("chatter(%s) at (%s) send: %s\n", senderId, createTime, content));
|
|
||||||
}
|
|
||||||
writer.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -31,5 +31,9 @@ public class LarkConfig {
|
|||||||
@Value("${lark.alert.user-open-ids}")
|
@Value("${lark.alert.user-open-ids}")
|
||||||
private String[] alertUserOpenIds;
|
private String[] alertUserOpenIds;
|
||||||
|
|
||||||
|
@Value("${lark.alert.exception-card}")
|
||||||
|
private String exceptionCards;
|
||||||
|
|
||||||
|
@Value("${lark.alert.success-card}")
|
||||||
|
private String successCards;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user