提交
This commit is contained in:
Binary file not shown.
@@ -40,6 +40,21 @@
|
|||||||
<artifactId>jakarta.servlet-api</artifactId>
|
<artifactId>jakarta.servlet-api</artifactId>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.tashow.cloud</groupId>
|
||||||
|
<artifactId>tashow-data-mybatis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jodd</groupId>
|
||||||
|
<artifactId>jodd-util</artifactId>
|
||||||
|
<version>6.3.0</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -1,25 +1,24 @@
|
|||||||
package com.tashow.cloud.mq.core;
|
package com.tashow.cloud.mq.core;
|
||||||
|
import lombok.Data;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MQ消息基类,所有消息类型都应该继承此类
|
* MQ消息基类,
|
||||||
|
*
|
||||||
*
|
*
|
||||||
* @author tashow
|
* @author tashow
|
||||||
*/
|
*/
|
||||||
|
@Data
|
||||||
public class BaseMqMessage implements Serializable {
|
public class BaseMqMessage implements Serializable {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 消息ID,默认为UUID
|
* 消息ID,默认为UUID
|
||||||
*/
|
*/
|
||||||
private Integer id = UUID.randomUUID().hashCode();
|
private Integer id = UUID.randomUUID().hashCode();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 消息状态码
|
* 消息状态码
|
||||||
*/
|
*/
|
||||||
@@ -44,72 +43,4 @@ public class BaseMqMessage implements Serializable {
|
|||||||
* 扩展数据
|
* 扩展数据
|
||||||
*/
|
*/
|
||||||
private Map<String, Object> extraData = new HashMap<>();
|
private Map<String, Object> extraData = new HashMap<>();
|
||||||
|
|
||||||
/**
|
|
||||||
* 增加重试次数
|
|
||||||
*/
|
|
||||||
public void incrementRetryCount() {
|
|
||||||
if (retryCount == null) {
|
|
||||||
retryCount = 0;
|
|
||||||
}
|
|
||||||
retryCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加额外数据
|
|
||||||
*/
|
|
||||||
public void addExtraData(String key, Object value) {
|
|
||||||
if (extraData == null) {
|
|
||||||
extraData = new HashMap<>();
|
|
||||||
}
|
|
||||||
extraData.put(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setId(Integer id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getStatusCode() {
|
|
||||||
return statusCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStatusCode(Integer statusCode) {
|
|
||||||
this.statusCode = statusCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getRetryCount() {
|
|
||||||
return retryCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRetryCount(Integer retryCount) {
|
|
||||||
this.retryCount = retryCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getErrorMessage() {
|
|
||||||
return errorMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setErrorMessage(String errorMessage) {
|
|
||||||
this.errorMessage = errorMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getCreateTime() {
|
|
||||||
return createTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCreateTime(Date createTime) {
|
|
||||||
this.createTime = createTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, Object> getExtraData() {
|
|
||||||
return extraData;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setExtraData(Map<String, Object> extraData) {
|
|
||||||
this.extraData = extraData;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
package com.tashow.cloud.mq.core;
|
|
||||||
|
|
||||||
import org.springframework.amqp.rabbit.connection.CorrelationData;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 自定义关联数据,用于存储额外的消息数据
|
|
||||||
*
|
|
||||||
* @author tashow
|
|
||||||
*/
|
|
||||||
public class CustomCorrelationData extends CorrelationData {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 消息内容
|
|
||||||
*/
|
|
||||||
private final String messageContent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 构造函数
|
|
||||||
*
|
|
||||||
* @param id 关联ID
|
|
||||||
* @param messageContent 消息内容
|
|
||||||
*/
|
|
||||||
public CustomCorrelationData(String id, String messageContent) {
|
|
||||||
super(id);
|
|
||||||
this.messageContent = messageContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取消息内容
|
|
||||||
*
|
|
||||||
* @return 消息内容
|
|
||||||
*/
|
|
||||||
public String getMessageContent() {
|
|
||||||
return messageContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "CustomCorrelationData{" +
|
|
||||||
"id='" + getId() + '\'' +
|
|
||||||
", messageContent='" + messageContent + '\'' +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +1,31 @@
|
|||||||
package com.tashow.cloud.mq.handler;
|
package com.tashow.cloud.mq.handler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 消息发送失败记录处理接口
|
* 消息记录处理接口
|
||||||
*
|
*
|
||||||
* @author tashow
|
* @author tashow
|
||||||
*/
|
*/
|
||||||
public interface FailRecordHandler {
|
public interface FailRecordHandler {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存消息发送失败记录
|
* 保存消息记录
|
||||||
*
|
*
|
||||||
* @param correlationId 关联ID
|
|
||||||
* @param exchange 交换机
|
* @param exchange 交换机
|
||||||
* @param routingKey 路由键
|
* @param routingKey 路由键
|
||||||
* @param cause 失败原因
|
* @param cause 失败原因,可为null
|
||||||
* @param messageContent 消息内容
|
* @param messageContent 消息内容
|
||||||
|
* @param status 状态:0-未处理,1-处理成功,2-处理失败
|
||||||
*/
|
*/
|
||||||
void saveFailRecord(String correlationId, String exchange, String routingKey, String cause, String messageContent);
|
void saveMessageRecord(Integer id, String exchange, String routingKey, String cause, String messageContent, int status);
|
||||||
|
/**
|
||||||
|
* 更新消息状态
|
||||||
|
*
|
||||||
|
* @param id 关联ID
|
||||||
|
*/
|
||||||
|
void updateMessageStatus(Integer id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查是否达到告警阈值
|
* 更新消息状态并设置失败原因
|
||||||
*
|
|
||||||
* @return 是否需要告警
|
|
||||||
*/
|
|
||||||
default boolean checkAlertThreshold() {
|
|
||||||
return checkAlertThreshold(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否达到告警阈值,带错误信息
|
|
||||||
*
|
|
||||||
* @param cause 错误原因
|
|
||||||
* @return 是否需要告警
|
|
||||||
*/
|
*/
|
||||||
default boolean checkAlertThreshold(String cause) {
|
void updateMessageStatusWithCause(Integer id, String causes);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
package com.tashow.cloud.mq.rabbitmq.config;
|
package com.tashow.cloud.mq.rabbitmq.config;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||||
@@ -13,7 +12,5 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
|||||||
@AutoConfiguration
|
@AutoConfiguration
|
||||||
@ConditionalOnClass(name = "org.springframework.amqp.rabbit.core.RabbitTemplate")
|
@ConditionalOnClass(name = "org.springframework.amqp.rabbit.core.RabbitTemplate")
|
||||||
public class RabbitMQAutoConfiguration extends RabbitMQConfiguration {
|
public class RabbitMQAutoConfiguration extends RabbitMQConfiguration {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(RabbitMQAutoConfiguration.class);
|
private static final Logger log = LoggerFactory.getLogger(RabbitMQAutoConfiguration.class);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -17,26 +17,6 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
@Configuration
|
@Configuration
|
||||||
@ConditionalOnClass(name = "org.springframework.amqp.rabbit.core.RabbitTemplate")
|
@ConditionalOnClass(name = "org.springframework.amqp.rabbit.core.RabbitTemplate")
|
||||||
public class RabbitMQConfiguration {
|
public class RabbitMQConfiguration {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(RabbitMQConfiguration.class);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化RabbitTemplate
|
|
||||||
*
|
|
||||||
* @param rabbitTemplate RabbitTemplate
|
|
||||||
*/
|
|
||||||
protected void initRabbitTemplate(RabbitTemplate rabbitTemplate) {
|
|
||||||
log.info("[MQ配置] 初始化RabbitTemplate: {}", rabbitTemplate);
|
|
||||||
// 启用消息发送到交换机确认机制
|
|
||||||
rabbitTemplate.setMandatory(true);
|
|
||||||
|
|
||||||
if (rabbitTemplate.isConfirmListener()) {
|
|
||||||
log.info("[MQ配置] 确认回调已正确配置");
|
|
||||||
} else {
|
|
||||||
log.error("[MQ配置] 确认回调配置失败");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建消息转换器
|
* 创建消息转换器
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
package com.tashow.cloud.mq.rabbitmq.consumer;
|
package com.tashow.cloud.mq.rabbitmq.consumer;
|
||||||
|
|
||||||
import com.rabbitmq.client.Channel;
|
import com.rabbitmq.client.Channel;
|
||||||
import com.tashow.cloud.mq.core.BaseMqMessage;
|
import com.tashow.cloud.mq.core.BaseMqMessage;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -16,136 +15,52 @@ import org.springframework.messaging.handler.annotation.Header;
|
|||||||
public abstract class AbstractRabbitMQConsumer<T extends BaseMqMessage> {
|
public abstract class AbstractRabbitMQConsumer<T extends BaseMqMessage> {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(AbstractRabbitMQConsumer.class);
|
private static final Logger log = LoggerFactory.getLogger(AbstractRabbitMQConsumer.class);
|
||||||
|
|
||||||
/**
|
|
||||||
* 消息状态:处理中
|
|
||||||
*/
|
|
||||||
public static final int STATUS_PROCESSING = 10;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 消息状态:成功
|
* 消息状态:成功
|
||||||
*/
|
*/
|
||||||
public static final int STATUS_SUCCESS = 20;
|
public static final int STATUS_SUCCESS = 20;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 消息状态:失败
|
* 消息状态:消费异常
|
||||||
*/
|
*/
|
||||||
public static final int STATUS_ERROR = 30;
|
public static final int STATUS_SEND_EXCEPTION = 30;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理消息
|
* 埋点处理消息
|
||||||
*
|
*
|
||||||
* @param message 消息对象
|
* @param message 消息对象
|
||||||
* @return 处理结果,true表示处理成功,false表示处理失败
|
* @return 处理结果,true表示处理成功,false表示处理失败
|
||||||
*/
|
*/
|
||||||
public abstract boolean processMessage(T message);
|
public abstract boolean processMessage(T message);
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取消息重试次数
|
|
||||||
*
|
|
||||||
* @param message 消息对象
|
|
||||||
* @return 重试次数
|
|
||||||
*/
|
|
||||||
public Integer getRetryCount(T message) {
|
|
||||||
return message.getRetryCount() != null ? message.getRetryCount() : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新消息状态
|
|
||||||
*
|
|
||||||
* @param message 消息对象
|
|
||||||
*/
|
|
||||||
public abstract void updateMessageStatus(T message);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新消息重试次数
|
|
||||||
*
|
|
||||||
* @param message 消息对象
|
|
||||||
*/
|
|
||||||
public abstract void updateRetryCount(T message);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存消息到数据库
|
|
||||||
*
|
|
||||||
* @param message 消息对象
|
|
||||||
* @return 保存结果
|
|
||||||
*/
|
|
||||||
public abstract boolean saveToDatabase(T message);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存消息到失败记录
|
|
||||||
*
|
|
||||||
* @param message 消息对象
|
|
||||||
* @param cause 失败原因
|
|
||||||
*/
|
|
||||||
public abstract void saveToFailRecord(T message, String cause);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取最大允许重试次数
|
|
||||||
*
|
|
||||||
* @return 最大重试次数
|
|
||||||
*/
|
|
||||||
public int getMaxRetryAllowed() {
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 消息处理入口
|
* 消息处理入口
|
||||||
*
|
*
|
||||||
* @param message 消息对象
|
* @param message 消息对象
|
||||||
* @param channel 通道
|
* @param channel 通道
|
||||||
* @param deliveryTag 投递标签
|
* @param deliveryTag 投递标签
|
||||||
*/
|
*/
|
||||||
public void onMessage(T message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) {
|
public void onMessage(T message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) {
|
||||||
Integer dbRetryCount = getRetryCount(message);
|
message.setStatusCode(STATUS_SUCCESS);
|
||||||
message.setRetryCount(dbRetryCount);
|
|
||||||
|
|
||||||
if (message.getRetryCount() != null && message.getRetryCount() >= getMaxRetryAllowed()) {
|
|
||||||
message.setStatusCode(STATUS_ERROR);
|
|
||||||
message.addExtraData("errorMessage", "已达到最大重试次数");
|
|
||||||
saveToFailRecord(message, "已达到最大重试次数");
|
|
||||||
safeChannelAck(channel, deliveryTag);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("[MQ消费者] 收到消息: {}, 当前重试次数: {}/{}", message, message.getRetryCount(), getMaxRetryAllowed());
|
|
||||||
message.setStatusCode(STATUS_PROCESSING);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
boolean result = processMessage(message);
|
if(true){
|
||||||
if (result) {
|
throw new RuntimeException("测试异常");
|
||||||
message.setStatusCode(STATUS_SUCCESS);
|
|
||||||
updateMessageStatus(message);
|
|
||||||
log.info("[MQ消费者] 消息处理成功,状态已更新为成功: {}", message.getId());
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("消息处理失败");
|
|
||||||
}
|
}
|
||||||
|
processMessage(message);
|
||||||
safeChannelAck(channel, deliveryTag);
|
safeChannelAck(channel, deliveryTag);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
message.setStatusCode(STATUS_ERROR);
|
message.setStatusCode(STATUS_SEND_EXCEPTION);
|
||||||
message.addExtraData("errorMessage", e.getMessage());
|
processMessage( message);
|
||||||
message.setErrorMessage(e.getMessage());
|
safeChannelAck(channel, deliveryTag);
|
||||||
log.error("[MQ消费者] 消息处理失败: {}, 错误: {}", message.getId(), e.getMessage());
|
|
||||||
|
|
||||||
message.incrementRetryCount();
|
|
||||||
updateRetryCount(message);
|
|
||||||
|
|
||||||
if (message.getRetryCount() >= getMaxRetryAllowed()) {
|
|
||||||
saveToDatabase(message);
|
|
||||||
log.warn("[MQ消费者] 消息已达到最大重试次数: {}, 确认消息并保存到失败记录表", message.getRetryCount());
|
|
||||||
saveToFailRecord(message, e.getMessage());
|
|
||||||
safeChannelAck(channel, deliveryTag);
|
|
||||||
} else {
|
|
||||||
log.info("[MQ消费者] 消息将重新入队重试: {}, 当前重试次数: {}", message.getId(), message.getRetryCount());
|
|
||||||
safeChannelNack(channel, deliveryTag, false, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 安全确认消息
|
* 安全确认消息
|
||||||
*
|
*
|
||||||
* @param channel 通道
|
* @param channel 通道
|
||||||
* @param deliveryTag 投递标签
|
* @param deliveryTag 投递标签
|
||||||
*/
|
*/
|
||||||
protected void safeChannelAck(Channel channel, long deliveryTag) {
|
protected void safeChannelAck(Channel channel, long deliveryTag) {
|
||||||
@@ -159,10 +74,10 @@ public abstract class AbstractRabbitMQConsumer<T extends BaseMqMessage> {
|
|||||||
/**
|
/**
|
||||||
* 安全拒绝消息
|
* 安全拒绝消息
|
||||||
*
|
*
|
||||||
* @param channel 通道
|
* @param channel 通道
|
||||||
* @param deliveryTag 投递标签
|
* @param deliveryTag 投递标签
|
||||||
* @param multiple 是否批量
|
* @param multiple 是否批量
|
||||||
* @param requeue 是否重新入队
|
* @param requeue 是否重新入队
|
||||||
*/
|
*/
|
||||||
protected void safeChannelNack(Channel channel, long deliveryTag, boolean multiple, boolean requeue) {
|
protected void safeChannelNack(Channel channel, long deliveryTag, boolean multiple, boolean requeue) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
/**
|
|
||||||
* 占位符,无特殊逻辑
|
|
||||||
*/
|
|
||||||
package com.tashow.cloud.mq.rabbitmq.core;
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
/**
|
|
||||||
* 消息队列,基于 RabbitMQ 提供
|
|
||||||
*/
|
|
||||||
package com.tashow.cloud.mq.rabbitmq;
|
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
package com.tashow.cloud.mq.rabbitmq.producer;
|
package com.tashow.cloud.mq.rabbitmq.producer;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.tashow.cloud.common.util.json.JsonUtils;
|
||||||
import com.tashow.cloud.mq.core.BaseMqMessage;
|
import com.tashow.cloud.mq.core.BaseMqMessage;
|
||||||
import com.tashow.cloud.mq.core.CustomCorrelationData;
|
|
||||||
import com.tashow.cloud.mq.handler.FailRecordHandler;
|
import com.tashow.cloud.mq.handler.FailRecordHandler;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.amqp.core.ReturnedMessage;
|
|
||||||
import org.springframework.amqp.rabbit.connection.CorrelationData;
|
import org.springframework.amqp.rabbit.connection.CorrelationData;
|
||||||
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@@ -35,11 +34,9 @@ public abstract class AbstractRabbitMQProducer<T extends BaseMqMessage>
|
|||||||
*/
|
*/
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void initRabbitTemplate() {
|
public void initRabbitTemplate() {
|
||||||
log.info("[MQ生产者] 初始化RabbitTemplate: {}", rabbitTemplate);
|
|
||||||
rabbitTemplate.setMandatory(true);
|
rabbitTemplate.setMandatory(true);
|
||||||
rabbitTemplate.setReturnsCallback(this);
|
rabbitTemplate.setReturnsCallback(this);
|
||||||
rabbitTemplate.setConfirmCallback(this);
|
rabbitTemplate.setConfirmCallback(this);
|
||||||
|
|
||||||
if (rabbitTemplate.isConfirmListener()) {
|
if (rabbitTemplate.isConfirmListener()) {
|
||||||
log.info("[MQ生产者] 确认回调已正确配置");
|
log.info("[MQ生产者] 确认回调已正确配置");
|
||||||
} else {
|
} else {
|
||||||
@@ -47,51 +44,38 @@ public abstract class AbstractRabbitMQProducer<T extends BaseMqMessage>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 将消息转换为字符串
|
|
||||||
*
|
|
||||||
* @param message 消息对象
|
|
||||||
* @return 消息字符串
|
|
||||||
*/
|
|
||||||
protected abstract String convertMessageToString(T message);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 异步发送消息,自动生成correlationId
|
|
||||||
*
|
|
||||||
* @param message 消息对象
|
|
||||||
*/
|
|
||||||
public void asyncSendMessage(T message) {
|
|
||||||
String correlationId = UUID.randomUUID().toString();
|
|
||||||
asyncSendMessage(message, correlationId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 异步发送消息,使用指定的correlationId
|
* 异步发送消息,使用指定的correlationId
|
||||||
*
|
*
|
||||||
* @param message 消息对象
|
* @param message 消息对象
|
||||||
* @param correlationId 关联ID
|
|
||||||
*/
|
*/
|
||||||
public void asyncSendMessage(T message, String correlationId) {
|
public void asyncSendMessage(T message) {
|
||||||
log.info("[MQ生产者] 准备发送消息: {}, correlationId: {}", message, correlationId);
|
|
||||||
try {
|
try {
|
||||||
String messageJson = convertMessageToString(message);
|
String messageJson = JsonUtils.toJsonString(message);
|
||||||
CustomCorrelationData correlationData = new CustomCorrelationData(correlationId, messageJson);
|
CorrelationData correlationData = new CorrelationData(messageJson);
|
||||||
|
failRecordHandler.saveMessageRecord(
|
||||||
|
message.getId(),
|
||||||
|
getExchange(),
|
||||||
|
getRoutingKey(),
|
||||||
|
null,
|
||||||
|
messageJson,
|
||||||
|
0
|
||||||
|
);
|
||||||
rabbitTemplate.convertAndSend(getExchange(), getRoutingKey(), message, correlationData);
|
rabbitTemplate.convertAndSend(getExchange(), getRoutingKey(), message, correlationData);
|
||||||
log.info("[MQ生产者] 消息发送完成: {}, 状态: {}, 重试次数: {}, correlationId: {}",
|
|
||||||
message.getId(), message.getStatusCode(), message.getRetryCount(), correlationId);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[MQ生产者] 消息发送异常: {}, correlationId: {}", e.getMessage(), correlationId, e);
|
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取交换机名称
|
* 获取交换机名称
|
||||||
*
|
*
|
||||||
* @return 交换机名称
|
* @return 交换机名称
|
||||||
*/
|
*/
|
||||||
public abstract String getExchange();
|
public abstract String getExchange();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取路由键
|
* 获取路由键
|
||||||
*
|
*
|
||||||
@@ -101,45 +85,13 @@ public abstract class AbstractRabbitMQProducer<T extends BaseMqMessage>
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
|
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
|
||||||
|
JSONObject jsonObject = JSON.parseObject(correlationData.getId());
|
||||||
|
Integer id = jsonObject.getInteger("id");
|
||||||
if (ack) {
|
if (ack) {
|
||||||
log.info("[MQ生产者] 消息发送确认成功: {}", correlationData.getId());
|
failRecordHandler.updateMessageStatus(id);
|
||||||
} else {
|
} else {
|
||||||
log.error("[MQ生产者] 消息发送确认失败: {}, 原因: {}, correlationData={}",
|
failRecordHandler.updateMessageStatusWithCause(id,cause);
|
||||||
correlationData.getId(), cause, correlationData);
|
|
||||||
if (failRecordHandler != null && correlationData instanceof CustomCorrelationData) {
|
|
||||||
CustomCorrelationData customData = (CustomCorrelationData) correlationData;
|
|
||||||
String messageContent = customData.getMessageContent();
|
|
||||||
failRecordHandler.saveFailRecord(
|
|
||||||
correlationData.getId(),
|
|
||||||
getExchange(),
|
|
||||||
getRoutingKey(),
|
|
||||||
cause,
|
|
||||||
messageContent
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
log.warn("[MQ生产者] 未配置FailRecordHandler或非CustomCorrelationData类型, 无法保存失败记录");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void returnedMessage(ReturnedMessage returned) {
|
|
||||||
log.error("[MQ生产者] 消息路由失败: exchange={}, routingKey={}, replyCode={}, replyText={}, message={}",
|
|
||||||
returned.getExchange(),
|
|
||||||
returned.getRoutingKey(),
|
|
||||||
returned.getReplyCode(),
|
|
||||||
returned.getReplyText(),
|
|
||||||
new String(returned.getMessage().getBody()));
|
|
||||||
if (failRecordHandler != null) {
|
|
||||||
failRecordHandler.saveFailRecord(
|
|
||||||
returned.getMessage().getMessageProperties().getCorrelationId(),
|
|
||||||
returned.getExchange(),
|
|
||||||
returned.getRoutingKey(),
|
|
||||||
"路由失败: " + returned.getReplyText(),
|
|
||||||
new String(returned.getMessage().getBody())
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
log.warn("[MQ生产者] 未配置FailRecordHandler, 无法保存失败记录");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -15,15 +15,7 @@ public abstract class AbstractMessageRetryTask<T> {
|
|||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(AbstractMessageRetryTask.class);
|
private static final Logger log = LoggerFactory.getLogger(AbstractMessageRetryTask.class);
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理中状态码
|
|
||||||
*/
|
|
||||||
public static final int STATUS_PROCESSING = 3;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 失败状态码
|
|
||||||
*/
|
|
||||||
public static final int STATUS_FAILED = 4;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取消息重试服务
|
* 获取消息重试服务
|
||||||
@@ -38,15 +30,7 @@ public abstract class AbstractMessageRetryTask<T> {
|
|||||||
* @param record 记录对象
|
* @param record 记录对象
|
||||||
* @return 记录ID
|
* @return 记录ID
|
||||||
*/
|
*/
|
||||||
protected abstract String getRecordId(T record);
|
protected abstract Integer getRecordId(T record);
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取关联ID
|
|
||||||
*
|
|
||||||
* @param record 记录对象
|
|
||||||
* @return 关联ID
|
|
||||||
*/
|
|
||||||
protected abstract String getCorrelationId(T record);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行重试
|
* 执行重试
|
||||||
@@ -54,36 +38,10 @@ public abstract class AbstractMessageRetryTask<T> {
|
|||||||
public void retryFailedMessages() {
|
public void retryFailedMessages() {
|
||||||
try {
|
try {
|
||||||
List<T> unprocessedRecords = getMessageRetryService().getUnprocessedRecords();
|
List<T> unprocessedRecords = getMessageRetryService().getUnprocessedRecords();
|
||||||
if (unprocessedRecords.isEmpty()) {
|
|
||||||
log.info("[MQ重试] 没有需要重试的消息");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("[MQ重试] 本次需要重试的消息数量: {}", unprocessedRecords.size());
|
|
||||||
for (T record : unprocessedRecords) {
|
for (T record : unprocessedRecords) {
|
||||||
try {
|
Integer recordId = getRecordId(record);
|
||||||
// 先将状态更新为处理中,避免其他实例重复处理
|
getMessageRetryService().retryFailedMessage(recordId);
|
||||||
if (!getMessageRetryService().updateStatus(record, STATUS_PROCESSING)) {
|
|
||||||
continue; // 如果更新失败,跳过当前记录
|
|
||||||
}
|
|
||||||
|
|
||||||
// 执行重试
|
|
||||||
String recordId = getRecordId(record);
|
|
||||||
boolean success = getMessageRetryService().retryFailedMessage(recordId);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
log.info("[MQ重试] 消息重试成功: {}", getCorrelationId(record));
|
|
||||||
} else {
|
|
||||||
log.warn("[MQ重试] 消息重试失败: {}", getCorrelationId(record));
|
|
||||||
getMessageRetryService().updateStatus(record, STATUS_FAILED);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 发生异常时,更新状态为失败
|
|
||||||
getMessageRetryService().updateStatus(record, STATUS_FAILED);
|
|
||||||
log.error("[MQ重试] 重试消息异常: {}", getCorrelationId(record), e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
log.info("[MQ重试] 消息重试任务完成");
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[MQ重试] 执行消息重试任务异常", e);
|
log.error("[MQ重试] 执行消息重试任务异常", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,14 +23,6 @@ public interface MessageRetryService<T> {
|
|||||||
* @param recordId 记录ID
|
* @param recordId 记录ID
|
||||||
* @return 重试结果
|
* @return 重试结果
|
||||||
*/
|
*/
|
||||||
boolean retryFailedMessage(String recordId);
|
void retryFailedMessage(Integer recordId);
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新记录状态
|
|
||||||
*
|
|
||||||
* @param record 记录对象
|
|
||||||
* @param status 记录状态
|
|
||||||
* @return 更新结果
|
|
||||||
*/
|
|
||||||
boolean updateStatus(T record, int status);
|
|
||||||
}
|
}
|
||||||
@@ -63,7 +63,7 @@ public class ApiAccessLogInterceptor implements HandlerInterceptor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||||
// 打印 response 日志
|
// 打印 response 日志ss
|
||||||
if (!SpringUtils.isProd()) {
|
if (!SpringUtils.isProd()) {
|
||||||
StopWatch stopWatch = (StopWatch) request.getAttribute(ATTRIBUTE_STOP_WATCH);
|
StopWatch stopWatch = (StopWatch) request.getAttribute(ATTRIBUTE_STOP_WATCH);
|
||||||
stopWatch.stop();
|
stopWatch.stop();
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
package com.tashow.cloud.app.config;
|
package com.tashow.cloud.app.config;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
package com.tashow.cloud.app.config;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 飞书客户端配置
|
|
||||||
* 用于初始化FeiShuAlertClient的相关依赖
|
|
||||||
*/
|
|
||||||
@Configuration
|
|
||||||
public class FeiShuClientConfig {
|
|
||||||
@Autowired
|
|
||||||
private StringRedisTemplate stringRedisTemplate;
|
|
||||||
/* @PostConstruct
|
|
||||||
public void initFeiShuClient() {
|
|
||||||
FeiShuAlertClient.setRedisTemplate(stringRedisTemplate);
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
@@ -1,4 +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 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;
|
||||||
@@ -11,27 +12,27 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
public class FeishuController {
|
public class FeishuController {
|
||||||
private static 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 static final String ACTION_COMPLETE_ALARM = "complete_alarm";
|
||||||
|
|
||||||
private final FeiShuAlertClient feiShuAlertClient;
|
private final FeiShuAlertClient feiShuAlertClient;
|
||||||
private final FeiShuCardDataService feiShuCardDataService;
|
private final FeiShuCardDataService feiShuCardDataService;
|
||||||
private final LarkConfig larkConfig;
|
private final LarkConfig larkConfig;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public FeishuController(FeiShuAlertClient feiShuAlertClient,
|
public FeishuController(FeiShuAlertClient feiShuAlertClient, FeiShuCardDataService feiShuCardDataService, LarkConfig larkConfig) {
|
||||||
FeiShuCardDataService feiShuCardDataService,
|
|
||||||
LarkConfig larkConfig) {
|
|
||||||
this.feiShuAlertClient = feiShuAlertClient;
|
this.feiShuAlertClient = feiShuAlertClient;
|
||||||
this.feiShuCardDataService = feiShuCardDataService;
|
this.feiShuCardDataService = feiShuCardDataService;
|
||||||
this.larkConfig = larkConfig;
|
this.larkConfig = larkConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping("/card1")
|
@RequestMapping("/card")
|
||||||
@PermitAll
|
@PermitAll
|
||||||
public String card(@RequestBody JSONObject data) {
|
public String card(@RequestBody JSONObject data) {
|
||||||
try {
|
try {
|
||||||
@@ -41,15 +42,17 @@ public class FeishuController {
|
|||||||
if (value != null && ACTION_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);
|
||||||
|
templateData.put("open_id", data.getStr("open_id"));
|
||||||
|
templateData.put("complete_time", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||||
|
JSONObject fromValue = action.getJSONObject("form_value");
|
||||||
|
templateData.put("notes", fromValue.getStr("notes_input"));
|
||||||
return feiShuAlertClient.buildCardWithData(larkConfig.getSuccessCards(), templateData);
|
return feiShuAlertClient.buildCardWithData(larkConfig.getSuccessCards(), templateData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.containsKey("encrypt")) {
|
if (data.containsKey("encrypt")) {
|
||||||
Decryptor decryptor = new Decryptor(larkConfig.getEncryptKey());
|
Decryptor decryptor = new Decryptor(larkConfig.getEncryptKey());
|
||||||
return decryptor.decrypt(data.getStr("encrypt"));
|
return decryptor.decrypt(data.getStr("encrypt"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return "{}";
|
return "{}";
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("卡片处理异常", e);
|
log.error("卡片处理异常", e);
|
||||||
@@ -57,14 +60,5 @@ public class FeishuController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送并存储卡片消息
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,10 +13,6 @@ import java.util.Map;
|
|||||||
@RestController
|
@RestController
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class TestController {
|
public class TestController {
|
||||||
|
|
||||||
private final BuriedPointProducer buriedPointProducer;
|
|
||||||
private final BuriedPointMapper buriedPointMapper;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基础测试接口
|
* 基础测试接口
|
||||||
*/
|
*/
|
||||||
@@ -26,17 +22,4 @@ public class TestController {
|
|||||||
return "test";
|
return "test";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 测试埋点拦截器
|
|
||||||
* 这个接口会被埋点拦截器自动记录请求信息
|
|
||||||
*/
|
|
||||||
@GetMapping("/test/buried-point")
|
|
||||||
@PermitAll
|
|
||||||
public Map<String, Object> testBuriedPoint() {
|
|
||||||
Map<String, Object> result = new HashMap<>();
|
|
||||||
result.put("success", true);
|
|
||||||
result.put("message", "埋点拦截器测试成功");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
package com.tashow.cloud.app.ext;
|
|
||||||
|
|
||||||
import com.lark.oapi.core.request.EventReq;
|
|
||||||
import com.lark.oapi.core.response.EventResp;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class HttpTranslator {
|
|
||||||
|
|
||||||
private Map<String, List<String>> toHeaderMap(HttpServletRequest req) {
|
|
||||||
Map<String, List<String>> headers = new HashMap<>();
|
|
||||||
Enumeration<String> names = req.getHeaderNames();
|
|
||||||
while (names.hasMoreElements()) {
|
|
||||||
String name = names.nextElement();
|
|
||||||
List<String> values = Collections.list(req.getHeaders(name));
|
|
||||||
headers.put(name, values);
|
|
||||||
}
|
|
||||||
return headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EventReq translate(HttpServletRequest request) throws IOException {
|
|
||||||
String bodyStr = request.getReader().lines()
|
|
||||||
.collect(Collectors.joining(System.lineSeparator()));
|
|
||||||
EventReq req = new EventReq();
|
|
||||||
req.setHeaders(toHeaderMap(request));
|
|
||||||
req.setBody(bodyStr.getBytes(StandardCharsets.UTF_8));
|
|
||||||
req.setHttpPath(request.getRequestURI());
|
|
||||||
return req;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void write(HttpServletResponse response, EventResp eventResp) throws IOException {
|
|
||||||
response.setStatus(eventResp.getStatusCode());
|
|
||||||
eventResp.getHeaders().entrySet().stream().forEach(keyValues -> {
|
|
||||||
String key = keyValues.getKey();
|
|
||||||
List<String> values = keyValues.getValue();
|
|
||||||
values.stream().forEach(v -> response.addHeader(key, v));
|
|
||||||
});
|
|
||||||
if (eventResp.getBody() != null) {
|
|
||||||
response.getWriter().write(new String(eventResp.getBody()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package com.tashow.cloud.app.ext;
|
|
||||||
|
|
||||||
import com.lark.oapi.card.CardActionHandler;
|
|
||||||
import com.lark.oapi.core.request.EventReq;
|
|
||||||
import com.lark.oapi.core.response.EventResp;
|
|
||||||
import com.lark.oapi.event.EventDispatcher;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Servlet的适配器,用于适配基于Servlet技术栈实现的Web服务
|
|
||||||
*/
|
|
||||||
public class ServletAdapter {
|
|
||||||
|
|
||||||
private static final HttpTranslator HTTP_TRANSLATOR = new HttpTranslator();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理消息事件
|
|
||||||
*
|
|
||||||
* @param req
|
|
||||||
* @param response
|
|
||||||
* @param eventDispatcher
|
|
||||||
* @throws Throwable
|
|
||||||
*/
|
|
||||||
public void handleEvent(HttpServletRequest req, HttpServletResponse response,
|
|
||||||
EventDispatcher eventDispatcher) throws Throwable {
|
|
||||||
// 转换请求对象
|
|
||||||
EventReq eventReq = HTTP_TRANSLATOR.translate(req);
|
|
||||||
|
|
||||||
// 处理请求
|
|
||||||
EventResp resp = eventDispatcher.handle(eventReq);
|
|
||||||
|
|
||||||
// 回写结果
|
|
||||||
HTTP_TRANSLATOR.write(response, resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理卡片消息
|
|
||||||
*
|
|
||||||
* @param req
|
|
||||||
* @param response
|
|
||||||
* @param handler
|
|
||||||
* @throws Throwable
|
|
||||||
*/
|
|
||||||
public void handleCardAction(HttpServletRequest req, HttpServletResponse response,
|
|
||||||
CardActionHandler handler) throws Throwable {
|
|
||||||
// 转换请求对象
|
|
||||||
EventReq eventReq = HTTP_TRANSLATOR.translate(req);
|
|
||||||
|
|
||||||
// 处理请求
|
|
||||||
EventResp resp = handler.handle(eventReq);
|
|
||||||
|
|
||||||
// 回写结果
|
|
||||||
HTTP_TRANSLATOR.write(response, resp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -27,68 +27,20 @@ public class BuriedPointInterceptor implements HandlerInterceptor {
|
|||||||
|
|
||||||
private final BuriedPointProducer buriedPointProducer;
|
private final BuriedPointProducer buriedPointProducer;
|
||||||
|
|
||||||
private static final String ATTRIBUTE_STOPWATCH = "BuriedPoint.StopWatch";
|
|
||||||
private static final String ATTRIBUTE_REQUEST_ID = "BuriedPoint.RequestId";
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||||
if (!(handler instanceof HandlerMethod)) {
|
if (!(handler instanceof HandlerMethod handlerMethod)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
BuriedMessages message = new BuriedMessages(
|
||||||
try {
|
request,
|
||||||
StopWatch stopWatch = new StopWatch();
|
handlerMethod
|
||||||
stopWatch.start();
|
);
|
||||||
request.setAttribute(ATTRIBUTE_STOPWATCH, stopWatch);
|
|
||||||
|
|
||||||
int requestId = (int)(Math.abs(IdUtil.getSnowflakeNextId()) % Integer.MAX_VALUE);
|
|
||||||
request.setAttribute(ATTRIBUTE_REQUEST_ID, requestId);
|
|
||||||
HandlerMethod handlerMethod = (HandlerMethod) handler;
|
|
||||||
String method = request.getMethod() + " " + request.getRequestURI()+ JsonUtils.toJsonString(request.getParameterMap());
|
|
||||||
String controllerName = handlerMethod.getBeanType().getSimpleName();
|
|
||||||
String actionName = handlerMethod.getMethod().getName();
|
|
||||||
BuriedMessages message = new BuriedMessages();
|
|
||||||
message.setId(requestId);
|
|
||||||
message.setEventTime(new java.util.Date());
|
|
||||||
message.setService(SpringUtils.getApplicationName());
|
|
||||||
message.setMethod(method);
|
|
||||||
message.setUserId(getUserId(request));
|
|
||||||
message.setSessionId(request.getSession().getId());
|
|
||||||
message.setClientIp(ServletUtils.getClientIP(request));
|
|
||||||
message.setServerIp(getServerIp());
|
|
||||||
message.setEventType("API_REQUEST_START");
|
|
||||||
message.setPagePath(controllerName + "#" + actionName);
|
|
||||||
message.setStatusCode(BuriedMessages.STATUS_PROCESSING);
|
|
||||||
buriedPointProducer.asyncSendMessage(message);
|
buriedPointProducer.asyncSendMessage(message);
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.debug("[埋点] 收集请求开始数据: {}", message);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("[埋点] 埋点数据收集异常", e);
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前登录用户ID
|
|
||||||
* 如果未登录返回匿名标识
|
|
||||||
*/
|
|
||||||
private String getUserId(HttpServletRequest request) {
|
|
||||||
Object userAttribute = request.getSession().getAttribute("USER_ID");
|
|
||||||
if (userAttribute != null) {
|
|
||||||
return userAttribute.toString();
|
|
||||||
}
|
|
||||||
return "anonymous";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取服务器IP
|
|
||||||
*/
|
|
||||||
private String getServerIp() {
|
|
||||||
try {
|
|
||||||
return InetAddress.getLocalHost().getHostAddress();
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
package com.tashow.cloud.app.mapper;
|
package com.tashow.cloud.app.mapper;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import com.tashow.cloud.app.model.BuriedPointFailRecord;
|
import com.tashow.cloud.app.model.MqMessageRecord;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 埋点消息发送失败记录Mapper接口
|
* 埋点消息发送记录Mapper接口
|
||||||
*/
|
*/
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface BuriedPointFailRecordMapper extends BaseMapper<BuriedPointFailRecord> {
|
public interface BuriedPointFailRecordMapper extends BaseMapper<MqMessageRecord> {
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,19 @@
|
|||||||
package com.tashow.cloud.app.model;
|
package com.tashow.cloud.app.model;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.tashow.cloud.app.mq.message.BuriedMessages;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 埋点数据实体类
|
* 埋点数据实体类
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
@TableName(value = "app_burying")
|
@TableName(value = "app_burying")
|
||||||
public class BuriedPoint {
|
public class BuriedPoint {
|
||||||
@@ -21,7 +22,6 @@ public class BuriedPoint {
|
|||||||
*/
|
*/
|
||||||
@TableId(value = "id", type = IdType.AUTO)
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
private Integer id;
|
private Integer id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 事件唯一ID
|
* 事件唯一ID
|
||||||
*/
|
*/
|
||||||
@@ -107,9 +107,22 @@ public class BuriedPoint {
|
|||||||
@TableField(value = "status")
|
@TableField(value = "status")
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|
||||||
/**
|
|
||||||
* 重试次数
|
public BuriedPoint(BuriedMessages message) {
|
||||||
*/
|
this.eventId = message.getId();
|
||||||
@TableField(value = "retry_count")
|
this.eventTime = System.currentTimeMillis();
|
||||||
private Integer retryCount;
|
this.userId = message.getUserId();
|
||||||
|
this.eventType = message.getEventType();
|
||||||
|
this.service = message.getService();
|
||||||
|
this.method = message.getMethod();
|
||||||
|
this.sessionId = message.getSessionId();
|
||||||
|
this.clientIp = message.getClientIp();
|
||||||
|
this.serverIp = message.getServerIp();
|
||||||
|
this.status = message.getStatusCode();
|
||||||
|
this.pagePath = message.getPagePath();
|
||||||
|
this.elementId = message.getElementId();
|
||||||
|
this.createTime = new Date();
|
||||||
|
this.updateTime = new Date();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,24 +11,17 @@ import java.util.Date;
|
|||||||
* 埋点消息发送失败记录实体类
|
* 埋点消息发送失败记录实体类
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@TableName("buried_point_fail_record")
|
@TableName("mq_message_record")
|
||||||
public class BuriedPointFailRecord {
|
public class MqMessageRecord {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 状态常量定义
|
* 状态常量定义
|
||||||
*/
|
*/
|
||||||
public static final int STATUS_UNPROCESSED = 0; // 未处理
|
public static final int STATUS_UNPROCESSED = 10; // 未处理
|
||||||
public static final int STATUS_PROCESSING = 1; // 处理中
|
public static final int STATUS_SUCCESS = 20; // 处理成功
|
||||||
public static final int STATUS_SUCCESS = 2; // 处理成功
|
public static final int STATUS_FAILED = 30; // 发送失败
|
||||||
public static final int STATUS_FAILED = 3; // 处理失败
|
@TableId
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
@TableId(type = IdType.AUTO)
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 消息关联ID
|
|
||||||
*/
|
|
||||||
private String correlationId;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 交换机名称
|
* 交换机名称
|
||||||
@@ -71,7 +71,7 @@ public class BuriedPointConfiguration implements WebMvcConfigurer {
|
|||||||
"/v3/api-docs/**",
|
"/v3/api-docs/**",
|
||||||
"/webjars/**",
|
"/webjars/**",
|
||||||
"/static/**",
|
"/static/**",
|
||||||
"/card1",
|
"/card",
|
||||||
"/error"
|
"/error"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package com.tashow.cloud.app.mq.consumer.buriedPoint;
|
package com.tashow.cloud.app.mq.consumer.buriedPoint;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
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.model.BuriedPoint;
|
import com.tashow.cloud.app.model.BuriedPoint;
|
||||||
import com.tashow.cloud.app.model.BuriedPointFailRecord;
|
import com.tashow.cloud.app.model.MqMessageRecord;
|
||||||
import com.tashow.cloud.app.mq.message.BuriedMessages;
|
import com.tashow.cloud.app.mq.message.BuriedMessages;
|
||||||
import com.tashow.cloud.app.service.feishu.BuriedPointMonitorService;
|
import com.tashow.cloud.app.service.feishu.BuriedPointMonitorService;
|
||||||
import com.rabbitmq.client.Channel;
|
import com.rabbitmq.client.Channel;
|
||||||
@@ -16,12 +16,8 @@ import org.springframework.amqp.support.AmqpHeaders;
|
|||||||
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.util.Date;
|
import java.util.Date;
|
||||||
import org.springframework.dao.DuplicateKeyException;
|
import org.springframework.dao.DuplicateKeyException;
|
||||||
import com.tashow.cloud.common.util.json.JsonUtils;
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 埋点消息消费者
|
* 埋点消息消费者
|
||||||
*/
|
*/
|
||||||
@@ -30,123 +26,39 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class BuriedPointConsumer extends AbstractRabbitMQConsumer<BuriedMessages> {
|
public class BuriedPointConsumer extends AbstractRabbitMQConsumer<BuriedMessages> {
|
||||||
|
|
||||||
private final BuriedPointMapper buriedPointMapper;
|
private final BuriedPointMapper buriedPointMapper;
|
||||||
private final BuriedPointFailRecordMapper buriedPointFailRecordMapper;
|
|
||||||
private final BuriedPointMonitorService buriedPointMonitorService;
|
private final BuriedPointMonitorService buriedPointMonitorService;
|
||||||
|
|
||||||
@Value("${spring.application.name:tashow-app}")
|
@Value("${spring.application.name:tashow-app}")
|
||||||
private String applicationName;
|
private String applicationName;
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMaxRetryAllowed() {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@RabbitHandler
|
@RabbitHandler
|
||||||
public void handleMessage(BuriedMessages message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) {
|
public void handleMessage(BuriedMessages message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) {
|
||||||
onMessage(message, channel, deliveryTag);
|
onMessage(message, channel, deliveryTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理埋点消息
|
||||||
|
* @param message 消息对象
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean processMessage(BuriedMessages message) {
|
public boolean processMessage(BuriedMessages message) {
|
||||||
return saveToDatabase(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Integer getRetryCount(BuriedMessages message) {
|
|
||||||
try {
|
|
||||||
BuriedPoint buriedPoint = buriedPointMapper.selectByEventId(message.getId());
|
|
||||||
if (buriedPoint != null && buriedPoint.getRetryCount() != null) {
|
|
||||||
if ((buriedPoint.getStatus() == BuriedMessages.STATUS_ERROR ||
|
|
||||||
buriedPoint.getStatus() == BuriedMessages.STATUS_PROCESSING)) {
|
|
||||||
return buriedPoint.getRetryCount() - 1;
|
|
||||||
}
|
|
||||||
return buriedPoint.getRetryCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
String correlationId = String.valueOf(message.getId());
|
|
||||||
BuriedPointFailRecord failRecord = findFailRecord(correlationId);
|
|
||||||
return failRecord != null ? failRecord.getRetryCount() : 0;
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("[埋点消费者] 获取重试次数失败", e);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据关联ID查找失败记录
|
|
||||||
*/
|
|
||||||
private BuriedPointFailRecord findFailRecord(String correlationId) {
|
|
||||||
LambdaQueryWrapper<BuriedPointFailRecord> queryWrapper = new LambdaQueryWrapper<>();
|
|
||||||
queryWrapper.eq(BuriedPointFailRecord::getCorrelationId, correlationId);
|
|
||||||
return buriedPointFailRecordMapper.selectOne(queryWrapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateMessageStatus(BuriedMessages message) {
|
|
||||||
try {
|
|
||||||
BuriedPoint buriedPoint = buriedPointMapper.selectByEventId(message.getId());
|
|
||||||
if (buriedPoint != null) {
|
|
||||||
buriedPoint.setStatus(message.getStatusCode());
|
|
||||||
buriedPoint.setUpdateTime(new Date());
|
|
||||||
buriedPoint.setRetryCount(message.getRetryCount());
|
|
||||||
buriedPointMapper.updateById(buriedPoint);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("[埋点消费者] 更新状态失败", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateRetryCount(BuriedMessages message) {
|
|
||||||
try {
|
|
||||||
BuriedPoint buriedPoint = buriedPointMapper.selectByEventId(message.getId());
|
|
||||||
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.setUpdateTime(new Date());
|
|
||||||
buriedPointMapper.updateById(buriedPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新失败记录表中的重试次数
|
|
||||||
*/
|
|
||||||
private void updateFailRecordRetryCount(BuriedPointFailRecord failRecord, BuriedMessages message) {
|
|
||||||
failRecord.setRetryCount(message.getRetryCount());
|
|
||||||
failRecord.setUpdateTime(new Date());
|
|
||||||
failRecord.setMessageContent(JsonUtils.toJsonString(message));
|
|
||||||
buriedPointFailRecordMapper.updateById(failRecord);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean saveToDatabase(BuriedMessages message) {
|
|
||||||
try {
|
try {
|
||||||
BuriedPoint existingPoint = buriedPointMapper.selectByEventId(message.getId());
|
BuriedPoint existingPoint = buriedPointMapper.selectByEventId(message.getId());
|
||||||
if (existingPoint != null) {
|
if (existingPoint != null) {
|
||||||
return updateExistingBuriedPoint(existingPoint, message);
|
existingPoint.setStatus(message.getStatusCode());
|
||||||
|
existingPoint.setUpdateTime(new Date());
|
||||||
|
return buriedPointMapper.updateById(existingPoint) > 0;
|
||||||
}
|
}
|
||||||
|
BuriedPoint buriedPoint = new BuriedPoint(message);
|
||||||
|
buriedPoint.setService(applicationName);
|
||||||
|
buriedPointMapper.insert(buriedPoint);
|
||||||
|
|
||||||
return createNewBuriedPoint(message);
|
if(buriedPoint.getStatus() == BuriedMessages.STATUS_ERROR){
|
||||||
|
buriedPointMonitorService.checkFailRecordsAndAlert("埋点数据处理异常");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
} catch (DuplicateKeyException e) {
|
} catch (DuplicateKeyException e) {
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -154,106 +66,4 @@ public class BuriedPointConsumer extends AbstractRabbitMQConsumer<BuriedMessages
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新已存在的埋点记录
|
|
||||||
*/
|
|
||||||
private boolean updateExistingBuriedPoint(BuriedPoint existingPoint, BuriedMessages message) {
|
|
||||||
existingPoint.setStatus(message.getStatusCode());
|
|
||||||
existingPoint.setUpdateTime(new Date());
|
|
||||||
|
|
||||||
if (message.getRetryCount() != null) {
|
|
||||||
int newRetryCount = Math.max(existingPoint.getRetryCount(), message.getRetryCount());
|
|
||||||
existingPoint.setRetryCount(newRetryCount);
|
|
||||||
message.setRetryCount(newRetryCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
return buriedPointMapper.updateById(existingPoint) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建新的埋点记录
|
|
||||||
*/
|
|
||||||
private boolean createNewBuriedPoint(BuriedMessages message) {
|
|
||||||
BuriedPoint buriedPoint = new BuriedPoint();
|
|
||||||
buriedPoint.setEventId(message.getId());
|
|
||||||
buriedPoint.setEventTime(System.currentTimeMillis());
|
|
||||||
buriedPoint.setUserId(message.getUserId());
|
|
||||||
buriedPoint.setEventType(message.getEventType());
|
|
||||||
buriedPoint.setService(applicationName);
|
|
||||||
buriedPoint.setMethod(message.getMethod());
|
|
||||||
buriedPoint.setSessionId(message.getSessionId());
|
|
||||||
buriedPoint.setClientIp(message.getClientIp());
|
|
||||||
buriedPoint.setServerIp(message.getServerIp());
|
|
||||||
buriedPoint.setStatus(message.getStatusCode());
|
|
||||||
buriedPoint.setRetryCount(message.getRetryCount());
|
|
||||||
buriedPoint.setPagePath(message.getPagePath());
|
|
||||||
buriedPoint.setElementId(message.getElementId());
|
|
||||||
buriedPoint.setDuration(message.getDuration());
|
|
||||||
buriedPoint.setCreateTime(new Date());
|
|
||||||
buriedPoint.setUpdateTime(new Date());
|
|
||||||
|
|
||||||
buriedPointMapper.insert(buriedPoint);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void saveToFailRecord(BuriedMessages message, String cause) {
|
|
||||||
try {
|
|
||||||
String correlationId = String.valueOf(message.getId());
|
|
||||||
BuriedPointFailRecord existingRecord = findFailRecord(correlationId);
|
|
||||||
|
|
||||||
if (existingRecord != null) {
|
|
||||||
updateExistingFailRecord(existingRecord, message, cause);
|
|
||||||
} 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();
|
|
||||||
failRecord.setCorrelationId(correlationId);
|
|
||||||
failRecord.setExchange(BuriedMessages.EXCHANGE);
|
|
||||||
failRecord.setRoutingKey(BuriedMessages.ROUTING_KEY);
|
|
||||||
failRecord.setCause(message.getErrorMessage() + cause);
|
|
||||||
failRecord.setMessageContent(JsonUtils.toJsonString(message));
|
|
||||||
failRecord.setRetryCount(message.getRetryCount());
|
|
||||||
failRecord.setStatus(BuriedPointFailRecord.STATUS_UNPROCESSED);
|
|
||||||
failRecord.setCreateTime(new Date());
|
|
||||||
failRecord.setUpdateTime(new Date());
|
|
||||||
buriedPointFailRecordMapper.insert(failRecord);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查失败记录数量并发送告警
|
|
||||||
*/
|
|
||||||
private void checkFailRecordsAndAlert() {
|
|
||||||
try {
|
|
||||||
buriedPointMonitorService.checkFailRecordsAndAlert("埋点处理异常,请检查系统");
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("[埋点消费者] 检查失败记录异常", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,93 +1,100 @@
|
|||||||
package com.tashow.cloud.app.mq.handler;
|
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.MqMessageRecord;
|
||||||
import com.tashow.cloud.app.service.feishu.BuriedPointMonitorService;
|
import com.tashow.cloud.app.service.feishu.BuriedPointMonitorService;
|
||||||
import com.tashow.cloud.mq.handler.FailRecordHandler;
|
import com.tashow.cloud.mq.handler.FailRecordHandler;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 埋点失败记录处理器
|
* MQ消息记录处理器
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class BuriedPointFailRecordHandler implements FailRecordHandler {
|
public class BuriedPointFailRecordHandler implements FailRecordHandler {
|
||||||
|
|
||||||
private final BuriedPointFailRecordMapper buriedPointFailRecordMapper;
|
private final BuriedPointFailRecordMapper buriedPointFailRecordMapper;
|
||||||
private final BuriedPointMonitorService buriedPointMonitorService;
|
private final BuriedPointMonitorService buriedPointMonitorService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存消息发送失败记录
|
* 保存消息记录=
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void saveFailRecord(String correlationId, String exchange, String routingKey, String cause, String messageContent) {
|
public void saveMessageRecord(Integer id, String exchange, String routingKey, String cause, String messageContent, int status) {
|
||||||
try {
|
try {
|
||||||
// 先查询是否已存在记录
|
MqMessageRecord existingRecord = findExistingRecord(id);
|
||||||
BuriedPointFailRecord existingRecord = findExistingRecord(correlationId);
|
|
||||||
|
|
||||||
if (existingRecord != null) {
|
if (existingRecord != null) {
|
||||||
updateExistingRecord(existingRecord, exchange, routingKey, cause, messageContent);
|
existingRecord.setRetryCount(existingRecord.getRetryCount() + 1);
|
||||||
|
existingRecord.setMessageContent(messageContent);
|
||||||
|
existingRecord.setStatus(status);
|
||||||
|
existingRecord.setCause(cause);
|
||||||
|
existingRecord.setUpdateTime(new Date());
|
||||||
|
buriedPointFailRecordMapper.updateById(existingRecord);
|
||||||
} else {
|
} else {
|
||||||
createNewFailRecord(correlationId, exchange, routingKey, cause, messageContent);
|
MqMessageRecord record = new MqMessageRecord();
|
||||||
checkAlertThreshold(cause);
|
record.setId(id);
|
||||||
|
record.setExchange(exchange);
|
||||||
|
record.setRoutingKey(routingKey);
|
||||||
|
record.setCause(cause);
|
||||||
|
record.setMessageContent(messageContent);
|
||||||
|
record.setRetryCount(0);
|
||||||
|
record.setStatus(status);
|
||||||
|
record.setCreateTime(new Date());
|
||||||
|
record.setUpdateTime(new Date());
|
||||||
|
buriedPointFailRecordMapper.insert(record);
|
||||||
|
if (status == MqMessageRecord.STATUS_FAILED) {
|
||||||
|
buriedPointMonitorService.checkFailRecordsAndAlert(cause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[埋点处理器] 保存失败记录异常", e);
|
log.error("[MQ消息处理器] 保存消息记录异常", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新消息状态
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void updateMessageStatus(Integer id) {
|
||||||
|
try {
|
||||||
|
MqMessageRecord record = findExistingRecord(id);
|
||||||
|
if (record != null) {
|
||||||
|
record.setStatus(MqMessageRecord.STATUS_SUCCESS);
|
||||||
|
record.setUpdateTime(new Date());
|
||||||
|
buriedPointFailRecordMapper.updateById(record);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[MQ消息处理器] 更新消息状态异常: {}", id, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新消息状态并设置失败原因
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void updateMessageStatusWithCause(Integer id, String cause) {
|
||||||
|
try {
|
||||||
|
MqMessageRecord record = findExistingRecord(id);
|
||||||
|
if (record != null) {
|
||||||
|
record.setStatus(MqMessageRecord.STATUS_FAILED);
|
||||||
|
record.setCause(cause);
|
||||||
|
record.setUpdateTime(new Date());
|
||||||
|
buriedPointFailRecordMapper.updateById(record);
|
||||||
|
buriedPointMonitorService.checkFailRecordsAndAlert(cause);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[MQ消息处理器] 更新消息状态和原因异常: {}", id, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查找已存在的失败记录
|
* 查找已存在的失败记录
|
||||||
*/
|
*/
|
||||||
private BuriedPointFailRecord findExistingRecord(String correlationId) {
|
private MqMessageRecord findExistingRecord(Integer id) {
|
||||||
LambdaQueryWrapper<BuriedPointFailRecord> queryWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<MqMessageRecord> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
queryWrapper.eq(BuriedPointFailRecord::getCorrelationId, correlationId);
|
queryWrapper.eq(MqMessageRecord::getId, id);
|
||||||
return buriedPointFailRecordMapper.selectOne(queryWrapper);
|
return buriedPointFailRecordMapper.selectOne(queryWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新已存在的失败记录
|
|
||||||
*/
|
|
||||||
private void updateExistingRecord(BuriedPointFailRecord record, String exchange, String routingKey,
|
|
||||||
String cause, String messageContent) {
|
|
||||||
record.setExchange(exchange);
|
|
||||||
record.setRoutingKey(routingKey);
|
|
||||||
record.setCause(cause);
|
|
||||||
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();
|
|
||||||
failRecord.setCorrelationId(correlationId);
|
|
||||||
failRecord.setExchange(exchange);
|
|
||||||
failRecord.setRoutingKey(routingKey);
|
|
||||||
failRecord.setCause(cause);
|
|
||||||
failRecord.setMessageContent(messageContent);
|
|
||||||
failRecord.setRetryCount(0);
|
|
||||||
failRecord.setStatus(BuriedPointFailRecord.STATUS_UNPROCESSED);
|
|
||||||
failRecord.setCreateTime(new Date());
|
|
||||||
failRecord.setUpdateTime(new Date());
|
|
||||||
buriedPointFailRecordMapper.insert(failRecord);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查是否达到告警阈值
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean checkAlertThreshold(String cause) {
|
|
||||||
return buriedPointMonitorService.checkFailRecordsAndAlert(cause);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,27 @@
|
|||||||
package com.tashow.cloud.app.mq.message;
|
package com.tashow.cloud.app.mq.message;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.IdUtil;
|
||||||
|
import com.tashow.cloud.common.util.json.JsonUtils;
|
||||||
|
import com.tashow.cloud.common.util.servlet.ServletUtils;
|
||||||
|
import com.tashow.cloud.common.util.spring.SpringUtils;
|
||||||
import com.tashow.cloud.mq.core.BaseMqMessage;
|
import com.tashow.cloud.mq.core.BaseMqMessage;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import org.springframework.web.method.HandlerMethod;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
|
import static com.tashow.cloud.web.apilog.core.interceptor.ApiAccessLogInterceptor.ATTRIBUTE_HANDLER_METHOD;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 埋点消息
|
* 埋点消息
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class BuriedMessages extends BaseMqMessage {
|
public class BuriedMessages extends BaseMqMessage {
|
||||||
|
private static final String ATTRIBUTE_REQUEST_ID = "BuriedPoint.RequestId";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 交换机名称
|
* 交换机名称
|
||||||
@@ -86,14 +98,45 @@ public class BuriedMessages extends BaseMqMessage {
|
|||||||
*/
|
*/
|
||||||
private String elementId;
|
private String elementId;
|
||||||
|
|
||||||
/**
|
|
||||||
* 持续时间
|
|
||||||
*/
|
|
||||||
private Long duration;
|
|
||||||
/**
|
/**
|
||||||
* 服务名称
|
* 服务名称
|
||||||
*/
|
*/
|
||||||
private String service;
|
private String service;
|
||||||
|
|
||||||
|
public BuriedMessages() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从请求创建埋点消息
|
||||||
|
*
|
||||||
|
* @param request HTTP请求
|
||||||
|
* @param handlerMethod 处理方法
|
||||||
|
*/
|
||||||
|
public BuriedMessages(HttpServletRequest request, HandlerMethod handlerMethod) {
|
||||||
|
try {
|
||||||
|
int requestId = (int)(Math.abs(IdUtil.getSnowflakeNextId()) % Integer.MAX_VALUE);
|
||||||
|
this.setId(requestId);
|
||||||
|
this.eventTime = new Date();
|
||||||
|
this.service = SpringUtils.getApplicationName();
|
||||||
|
this.method = request.getMethod() + " " + request.getRequestURI() +
|
||||||
|
JsonUtils.toJsonString(request.getParameterMap());
|
||||||
|
Object userId = request.getSession().getAttribute("USER_ID");
|
||||||
|
this.userId = userId != null ? userId.toString() : "anonymous";
|
||||||
|
this.sessionId = request.getSession().getId();
|
||||||
|
this.clientIp = ServletUtils.getClientIP(request);
|
||||||
|
try {
|
||||||
|
this.serverIp = InetAddress.getLocalHost().getHostAddress();
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
this.serverIp = "unknown";
|
||||||
|
}
|
||||||
|
String controllerName = handlerMethod.getBeanType().getSimpleName();
|
||||||
|
String actionName = handlerMethod.getMethod().getName();
|
||||||
|
this.pagePath = controllerName + "#" + actionName;
|
||||||
|
this.eventType = "API_REQUEST_START";
|
||||||
|
this.setStatusCode(STATUS_PROCESSING);
|
||||||
|
request.setAttribute(ATTRIBUTE_REQUEST_ID, this.getId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("创建埋点消息失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.tashow.cloud.app.mq.producer.buriedPoint;
|
|||||||
import com.tashow.cloud.app.mq.message.BuriedMessages;
|
import com.tashow.cloud.app.mq.message.BuriedMessages;
|
||||||
import com.tashow.cloud.common.util.json.JsonUtils;
|
import com.tashow.cloud.common.util.json.JsonUtils;
|
||||||
import com.tashow.cloud.mq.rabbitmq.producer.AbstractRabbitMQProducer;
|
import com.tashow.cloud.mq.rabbitmq.producer.AbstractRabbitMQProducer;
|
||||||
|
import org.springframework.amqp.core.ReturnedMessage;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -11,10 +12,14 @@ import org.springframework.stereotype.Component;
|
|||||||
@Component
|
@Component
|
||||||
public class BuriedPointProducer extends AbstractRabbitMQProducer<BuriedMessages> {
|
public class BuriedPointProducer extends AbstractRabbitMQProducer<BuriedMessages> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void returnedMessage(ReturnedMessage returned) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getExchange() {
|
public String getExchange() {
|
||||||
return "BuriedMessages.EXCHANGE";
|
return BuriedMessages.EXCHANGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -22,8 +27,5 @@ public class BuriedPointProducer extends AbstractRabbitMQProducer<BuriedMessages
|
|||||||
return BuriedMessages.ROUTING_KEY;
|
return BuriedMessages.ROUTING_KEY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String convertMessageToString(BuriedMessages message) {
|
|
||||||
return JsonUtils.toJsonString(message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,19 @@
|
|||||||
package com.tashow.cloud.app.service.feishu;
|
package com.tashow.cloud.app.service.feishu;
|
||||||
|
|
||||||
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.mapper.BuriedPointMapper;
|
||||||
|
import com.tashow.cloud.app.model.BuriedPoint;
|
||||||
|
import com.tashow.cloud.app.model.MqMessageRecord;
|
||||||
|
import com.tashow.cloud.app.mq.message.BuriedMessages;
|
||||||
import com.tashow.cloud.sdk.feishu.client.FeiShuAlertClient;
|
import com.tashow.cloud.sdk.feishu.client.FeiShuAlertClient;
|
||||||
import com.tashow.cloud.sdk.feishu.config.LarkConfig;
|
import com.tashow.cloud.sdk.feishu.config.LarkConfig;
|
||||||
import com.tashow.cloud.sdk.feishu.util.ChartImageGenerator;
|
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.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 埋点监控服务
|
* 埋点监控服务
|
||||||
@@ -20,11 +22,12 @@ import java.util.*;
|
|||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class BuriedPointMonitorService {
|
public class BuriedPointMonitorService {
|
||||||
|
|
||||||
private static final int ALERT_THRESHOLD = 3;
|
private static final int ALERT_THRESHOLD = 3;
|
||||||
private static final int MONITORING_HOURS = 12;
|
private static final int MONITORING_HOURS = 12;
|
||||||
|
private final Map<String, Long> alertCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private final BuriedPointFailRecordMapper buriedPointFailRecordMapper;
|
private final BuriedPointFailRecordMapper buriedPointFailRecordMapper;
|
||||||
|
private final BuriedPointMapper buriedPointMapper;
|
||||||
private final FeiShuAlertClient feiShuAlertClient;
|
private final FeiShuAlertClient feiShuAlertClient;
|
||||||
private final FeiShuCardDataService feiShuCardDataService;
|
private final FeiShuCardDataService feiShuCardDataService;
|
||||||
private final LarkConfig larkConfig;
|
private final LarkConfig larkConfig;
|
||||||
@@ -36,14 +39,19 @@ public class BuriedPointMonitorService {
|
|||||||
try {
|
try {
|
||||||
Date now = new Date();
|
Date now = new Date();
|
||||||
Date hoursAgo = getDateHoursAgo(now, MONITORING_HOURS);
|
Date hoursAgo = getDateHoursAgo(now, MONITORING_HOURS);
|
||||||
|
boolean sentAlert = false;
|
||||||
Long failCount = getUnprocessedFailCount(hoursAgo, now);
|
List<Date[]> timeRanges = getHourRanges(hoursAgo, now);
|
||||||
|
long mqFailCount = countFailures(buriedPointFailRecordMapper, MqMessageRecord.class, hoursAgo, now);
|
||||||
if (failCount > ALERT_THRESHOLD) {
|
long buriedFailCount = countFailures(buriedPointMapper, BuriedPoint.class, hoursAgo, now);
|
||||||
sendAlertMessage(failCount.intValue(), hoursAgo, now, cause);
|
if (mqFailCount > ALERT_THRESHOLD||buriedFailCount > ALERT_THRESHOLD) {
|
||||||
return true;
|
if (!hasRecentlySentAlert(cause)) {
|
||||||
|
sendAlert(mqFailCount, cause, getMqStats(timeRanges));
|
||||||
|
alertCache.put(cause, System.currentTimeMillis());
|
||||||
|
sentAlert = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
return sentAlert;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[埋点监控] 检查失败记录异常", e);
|
log.error("[埋点监控] 检查失败记录异常", e);
|
||||||
return false;
|
return false;
|
||||||
@@ -51,107 +59,174 @@ public class BuriedPointMonitorService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取未处理的失败记录数量
|
* 检查是否最近已发送过相同类型的告警
|
||||||
*/
|
*/
|
||||||
public Long getUnprocessedFailCount(Date startDate, Date endDate) {
|
private boolean hasRecentlySentAlert(String alertType) {
|
||||||
LambdaQueryWrapper<BuriedPointFailRecord> query = new LambdaQueryWrapper<>();
|
Long lastSentTime = alertCache.get(alertType);
|
||||||
query.ge(BuriedPointFailRecord::getCreateTime, startDate)
|
if (lastSentTime == null) {
|
||||||
.le(BuriedPointFailRecord::getCreateTime, endDate)
|
return false;
|
||||||
.eq(BuriedPointFailRecord::getStatus, BuriedPointFailRecord.STATUS_UNPROCESSED);
|
}
|
||||||
return buriedPointFailRecordMapper.selectCount(query);
|
|
||||||
|
long hourInMillis = MONITORING_HOURS * 60 * 60 * 1000L;
|
||||||
|
return (System.currentTimeMillis() - lastSentTime) < hourInMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取消息队列统计数据
|
||||||
|
*/
|
||||||
|
private List<ChartImageGenerator.MonitoringDataPoint> getMqStats(List<Date[]> timeRanges) {
|
||||||
|
Map<Date, Integer> successData = batchQueryMqStatus(timeRanges, MqMessageRecord.STATUS_SUCCESS);
|
||||||
|
Map<Date, Integer> failedData = batchQueryMqFailures(timeRanges);
|
||||||
|
|
||||||
|
SimpleDateFormat timeFormat = new SimpleDateFormat("HH:00");
|
||||||
|
return timeRanges.stream()
|
||||||
|
.map(range -> new ChartImageGenerator.MonitoringDataPoint(
|
||||||
|
timeFormat.format(range[0]),
|
||||||
|
successData.getOrDefault(range[0], 0),
|
||||||
|
failedData.getOrDefault(range[0], 0)
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送告警消息
|
* 获取埋点表统计数据
|
||||||
*/
|
*/
|
||||||
private void sendAlertMessage(int failCount, Date startDate, Date endDate, String errorMessage) {
|
private List<ChartImageGenerator.MonitoringDataPoint> getBuriedStats(List<Date[]> timeRanges) {
|
||||||
|
// 批量查询每个时间区间的数据
|
||||||
|
Map<Date, Integer> successData = batchQueryBuriedStatus(timeRanges, BuriedMessages.STATUS_SUCCESS);
|
||||||
|
Map<Date, Integer> failedData = batchQueryBuriedStatus(timeRanges, BuriedMessages.STATUS_ERROR);
|
||||||
|
SimpleDateFormat timeFormat = new SimpleDateFormat("HH:00");
|
||||||
|
return timeRanges.stream()
|
||||||
|
.map(range -> new ChartImageGenerator.MonitoringDataPoint(
|
||||||
|
timeFormat.format(range[0]),
|
||||||
|
successData.getOrDefault(range[0], 0),
|
||||||
|
failedData.getOrDefault(range[0], 0)
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询MQ状态数据
|
||||||
|
*/
|
||||||
|
private Map<Date, Integer> batchQueryMqStatus(List<Date[]> timeRanges, int status) {
|
||||||
|
Map<Date, Integer> result = new HashMap<>();
|
||||||
|
for (Date[] range : timeRanges) {
|
||||||
|
LambdaQueryWrapper<MqMessageRecord> query = new LambdaQueryWrapper<>();
|
||||||
|
query.ge(MqMessageRecord::getCreateTime, range[0])
|
||||||
|
.lt(MqMessageRecord::getCreateTime, range[1])
|
||||||
|
.eq(MqMessageRecord::getStatus, status);
|
||||||
|
result.put(range[0], buriedPointFailRecordMapper.selectCount(query).intValue());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量查询MQ失败数据
|
||||||
|
*/
|
||||||
|
private Map<Date, Integer> batchQueryMqFailures(List<Date[]> timeRanges) {
|
||||||
|
Map<Date, Integer> result = new HashMap<>();
|
||||||
|
for (Date[] range : timeRanges) {
|
||||||
|
LambdaQueryWrapper<MqMessageRecord> query = new LambdaQueryWrapper<>();
|
||||||
|
query.ge(MqMessageRecord::getCreateTime, range[0])
|
||||||
|
.lt(MqMessageRecord::getCreateTime, range[1])
|
||||||
|
.in(MqMessageRecord::getStatus, Arrays.asList(
|
||||||
|
MqMessageRecord.STATUS_UNPROCESSED, MqMessageRecord.STATUS_FAILED));
|
||||||
|
result.put(range[0], buriedPointFailRecordMapper.selectCount(query).intValue());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量查询埋点状态数据
|
||||||
|
*/
|
||||||
|
private Map<Date, Integer> batchQueryBuriedStatus(List<Date[]> timeRanges, int status) {
|
||||||
|
Map<Date, Integer> result = new HashMap<>();
|
||||||
|
for (Date[] range : timeRanges) {
|
||||||
|
LambdaQueryWrapper<BuriedPoint> query = new LambdaQueryWrapper<>();
|
||||||
|
query.ge(BuriedPoint::getCreateTime, range[0])
|
||||||
|
.lt(BuriedPoint::getCreateTime, range[1])
|
||||||
|
.eq(BuriedPoint::getStatus, status);
|
||||||
|
result.put(range[0], buriedPointMapper.selectCount(query).intValue());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算失败数量
|
||||||
|
*/
|
||||||
|
private <T> long countFailures(Object mapper, Class<T> entityClass, Date startDate, Date endDate) {
|
||||||
try {
|
try {
|
||||||
List<ChartImageGenerator.MonitoringDataPoint> monitoringData =
|
if (entityClass == BuriedPoint.class) {
|
||||||
queryHourlyFailRecordData(startDate, endDate);
|
LambdaQueryWrapper<BuriedPoint> query = new LambdaQueryWrapper<>();
|
||||||
|
query.ge(BuriedPoint::getCreateTime, startDate)
|
||||||
|
.le(BuriedPoint::getCreateTime, endDate)
|
||||||
|
.eq(BuriedPoint::getStatus, BuriedMessages.STATUS_ERROR);
|
||||||
|
return ((BuriedPointMapper)mapper).selectCount(query);
|
||||||
|
} else {
|
||||||
|
LambdaQueryWrapper<MqMessageRecord> query = new LambdaQueryWrapper<>();
|
||||||
|
query.ge(MqMessageRecord::getCreateTime, startDate)
|
||||||
|
.le(MqMessageRecord::getCreateTime, endDate)
|
||||||
|
.eq(MqMessageRecord::getStatus, MqMessageRecord.STATUS_FAILED);
|
||||||
|
return ((BuriedPointFailRecordMapper)mapper).selectCount(query);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
HashMap<String, Object> templateData = new HashMap<>();
|
/**
|
||||||
String chatId = larkConfig.getChatId();
|
* 发送告警
|
||||||
|
*/
|
||||||
|
private void sendAlert(long failCount, String alertMsg, List<ChartImageGenerator.MonitoringDataPoint> data) {
|
||||||
|
try {
|
||||||
|
String imageKey = feiShuAlertClient.uploadImage(data, alertMsg);
|
||||||
|
String title = alertMsg.split(":")[0].trim();
|
||||||
|
|
||||||
String imageKey = feiShuAlertClient.uploadImage(monitoringData, errorMessage);
|
Map<String, Object> templateData = Map.of(
|
||||||
|
"alert_title", title,
|
||||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
"image_key", Map.of("img_key", imageKey),
|
||||||
templateData.put("alert_title", "埋点数据异常告警");
|
"current_time", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()),
|
||||||
templateData.put("image_key", imageKey);
|
"fail_count", failCount
|
||||||
templateData.put("current_time", sdf.format(new Date()));
|
);
|
||||||
templateData.put("fail_count", failCount);
|
|
||||||
|
|
||||||
String messageId = feiShuAlertClient.sendCardMessage(
|
String messageId = feiShuAlertClient.sendCardMessage(
|
||||||
chatId,
|
larkConfig.getChatId(),
|
||||||
larkConfig.getExceptionCards(),
|
larkConfig.getExceptionCards(),
|
||||||
templateData
|
new HashMap<>(templateData)
|
||||||
);
|
);
|
||||||
feiShuCardDataService.saveCardData(messageId, templateData);
|
feiShuCardDataService.saveCardData(messageId, templateData);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[埋点监控] 发送告警失败", e);
|
log.error("[埋点监控] 发送告警失败: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询按小时统计的失败记录数据
|
* 获取小时范围列表
|
||||||
*/
|
*/
|
||||||
public List<ChartImageGenerator.MonitoringDataPoint> queryHourlyFailRecordData(Date startDate, Date endDate) {
|
private List<Date[]> getHourRanges(Date startDate, Date endDate) {
|
||||||
List<ChartImageGenerator.MonitoringDataPoint> result = new ArrayList<>();
|
List<Date[]> ranges = new ArrayList<>();
|
||||||
|
Calendar cal = Calendar.getInstance();
|
||||||
|
|
||||||
try {
|
cal.setTime(endDate);
|
||||||
Date limitedStartDate = getDateHoursAgo(endDate, MONITORING_HOURS);
|
cal.set(Calendar.MINUTE, 0);
|
||||||
Date actualStartDate = startDate.after(limitedStartDate) ? startDate : limitedStartDate;
|
cal.set(Calendar.SECOND, 0);
|
||||||
|
cal.set(Calendar.MILLISECOND, 0);
|
||||||
|
Date endHour = cal.getTime();
|
||||||
|
|
||||||
SimpleDateFormat sdf = new SimpleDateFormat("HH:00");
|
cal.add(Calendar.HOUR_OF_DAY, -(MONITORING_HOURS - 1));
|
||||||
Calendar calendar = Calendar.getInstance();
|
Date startHour = startDate.after(cal.getTime()) ? startDate : cal.getTime();
|
||||||
|
|
||||||
long hoursDiff = (endDate.getTime() - actualStartDate.getTime()) / (60 * 60 * 1000) + 1;
|
cal.setTime(startHour);
|
||||||
int hours = (int) Math.min(hoursDiff, MONITORING_HOURS);
|
while (!cal.getTime().after(endHour)) {
|
||||||
|
Date hourStart = cal.getTime();
|
||||||
|
cal.add(Calendar.HOUR_OF_DAY, 1);
|
||||||
|
Date hourEnd = cal.getTime().after(endDate) ? endDate : cal.getTime();
|
||||||
|
ranges.add(new Date[]{hourStart, hourEnd});
|
||||||
|
|
||||||
for (int i = 0; i < hours; i++) {
|
if (hourEnd.equals(endDate)) break;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
return ranges;
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取指定状态的记录数量
|
|
||||||
*/
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package com.tashow.cloud.app.service.impl;
|
|||||||
|
|
||||||
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.MqMessageRecord;
|
||||||
import com.tashow.cloud.app.mq.message.BuriedMessages;
|
import com.tashow.cloud.app.mq.message.BuriedMessages;
|
||||||
import com.tashow.cloud.app.mq.producer.buriedPoint.BuriedPointProducer;
|
import com.tashow.cloud.app.mq.producer.buriedPoint.BuriedPointProducer;
|
||||||
import com.tashow.cloud.common.util.json.JsonUtils;
|
import com.tashow.cloud.common.util.json.JsonUtils;
|
||||||
@@ -20,7 +20,7 @@ import java.util.List;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class BuriedPointFailRecordService implements MessageRetryService<BuriedPointFailRecord> {
|
public class BuriedPointFailRecordService implements MessageRetryService<MqMessageRecord> {
|
||||||
|
|
||||||
private final BuriedPointFailRecordMapper buriedPointFailRecordMapper;
|
private final BuriedPointFailRecordMapper buriedPointFailRecordMapper;
|
||||||
private final BuriedPointProducer buriedPointProducer;
|
private final BuriedPointProducer buriedPointProducer;
|
||||||
@@ -29,10 +29,10 @@ public class BuriedPointFailRecordService implements MessageRetryService<BuriedP
|
|||||||
* 获取未处理的失败记录
|
* 获取未处理的失败记录
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<BuriedPointFailRecord> getUnprocessedRecords() {
|
public List<MqMessageRecord> getUnprocessedRecords() {
|
||||||
LambdaQueryWrapper<BuriedPointFailRecord> queryWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<MqMessageRecord> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
queryWrapper.eq(BuriedPointFailRecord::getStatus, BuriedPointFailRecord.STATUS_UNPROCESSED)
|
queryWrapper.eq(MqMessageRecord::getStatus, MqMessageRecord.STATUS_FAILED)
|
||||||
.orderByAsc(BuriedPointFailRecord::getCreateTime);
|
.orderByAsc(MqMessageRecord::getCreateTime);
|
||||||
return buriedPointFailRecordMapper.selectList(queryWrapper);
|
return buriedPointFailRecordMapper.selectList(queryWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,39 +40,14 @@ public class BuriedPointFailRecordService implements MessageRetryService<BuriedP
|
|||||||
* 重试失败消息
|
* 重试失败消息
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean retryFailedMessage(String recordId) {
|
public void retryFailedMessage(Integer recordId) {
|
||||||
try {
|
try {
|
||||||
Long id = Long.valueOf(recordId);
|
Long id = Long.valueOf(recordId);
|
||||||
BuriedPointFailRecord record = buriedPointFailRecordMapper.selectById(id);
|
MqMessageRecord record = buriedPointFailRecordMapper.selectById(id);
|
||||||
|
|
||||||
|
|
||||||
BuriedMessages message = JsonUtils.parseObject(record.getMessageContent(), BuriedMessages.class);
|
BuriedMessages message = JsonUtils.parseObject(record.getMessageContent(), BuriedMessages.class);
|
||||||
if (message == null) {
|
buriedPointProducer.asyncSendMessage(message);
|
||||||
updateStatus(record, BuriedPointFailRecord.STATUS_FAILED);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
buriedPointProducer.asyncSendMessage(message, record.getCorrelationId());
|
|
||||||
updateStatus(record, BuriedPointFailRecord.STATUS_SUCCESS);
|
|
||||||
return true;
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("[埋点重试] 重试失败", e);
|
log.error("[埋点重试] 重试失败", e);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新记录状态
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean updateStatus(BuriedPointFailRecord record, int status) {
|
|
||||||
try {
|
|
||||||
record.setStatus(status);
|
|
||||||
record.setUpdateTime(new Date());
|
|
||||||
return buriedPointFailRecordMapper.updateById(record) > 0;
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("[埋点重试] 更新状态失败", e);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.tashow.cloud.app.task;
|
package com.tashow.cloud.app.task;
|
||||||
|
import com.tashow.cloud.app.model.MqMessageRecord;
|
||||||
import com.tashow.cloud.app.model.BuriedPointFailRecord;
|
|
||||||
import com.tashow.cloud.app.service.impl.BuriedPointFailRecordService;
|
import com.tashow.cloud.app.service.impl.BuriedPointFailRecordService;
|
||||||
import com.tashow.cloud.mq.retry.AbstractMessageRetryTask;
|
import com.tashow.cloud.mq.retry.AbstractMessageRetryTask;
|
||||||
import com.tashow.cloud.mq.retry.MessageRetryService;
|
import com.tashow.cloud.mq.retry.MessageRetryService;
|
||||||
@@ -15,7 +14,7 @@ import org.springframework.stereotype.Component;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class BuriedPointRetryTask extends AbstractMessageRetryTask<BuriedPointFailRecord> {
|
public class BuriedPointRetryTask extends AbstractMessageRetryTask<MqMessageRecord> {
|
||||||
|
|
||||||
private final BuriedPointFailRecordService buriedPointFailRecordService;
|
private final BuriedPointFailRecordService buriedPointFailRecordService;
|
||||||
|
|
||||||
@@ -24,22 +23,19 @@ public class BuriedPointRetryTask extends AbstractMessageRetryTask<BuriedPointFa
|
|||||||
* 每天凌晨执行一次
|
* 每天凌晨执行一次
|
||||||
*/
|
*/
|
||||||
@Scheduled(cron = "0 0 0 * * ?")
|
@Scheduled(cron = "0 0 0 * * ?")
|
||||||
|
// @Scheduled(cron = "0/10 * * * * ?")
|
||||||
public void execute() {
|
public void execute() {
|
||||||
retryFailedMessages();
|
retryFailedMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected MessageRetryService<BuriedPointFailRecord> getMessageRetryService() {
|
protected MessageRetryService<MqMessageRecord> getMessageRetryService() {
|
||||||
return buriedPointFailRecordService;
|
return buriedPointFailRecordService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getRecordId(BuriedPointFailRecord record) {
|
protected Integer getRecordId(MqMessageRecord record) {
|
||||||
return String.valueOf(record.getId());
|
return record.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getCorrelationId(BuriedPointFailRecord record) {
|
|
||||||
return record.getCorrelationId();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
3
tashow-sdk/tashow-feishu-sdk/.gitignore
vendored
Normal file
3
tashow-sdk/tashow-feishu-sdk/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.idea/
|
||||||
|
.DS_Store
|
||||||
|
src/main/java/com/tashow/cloud/sdk/feishu/client/chat_history.txt
|
||||||
@@ -5,22 +5,17 @@ import com.tashow.cloud.sdk.feishu.util.ChartImageGenerator;
|
|||||||
import com.lark.oapi.service.im.v1.model.ext.MessageTemplate;
|
import com.lark.oapi.service.im.v1.model.ext.MessageTemplate;
|
||||||
import com.lark.oapi.service.im.v1.model.ext.MessageTemplateData;
|
import com.lark.oapi.service.im.v1.model.ext.MessageTemplateData;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
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 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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 飞书告警客户端
|
* 飞书告警客户端
|
||||||
* 用于处理系统告警消息的发送
|
* 用于处理系统告警消息的发送
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class FeiShuAlertClient {
|
public class FeiShuAlertClient {
|
||||||
private final Logger log = LoggerFactory.getLogger(FeiShuAlertClient.class);
|
|
||||||
private final Client client;
|
private final Client client;
|
||||||
private final LarkConfig larkConfig;
|
private final LarkConfig larkConfig;
|
||||||
private final ChartImageGenerator chartImageGenerator;
|
private final ChartImageGenerator chartImageGenerator;
|
||||||
@@ -56,42 +51,6 @@ public class FeiShuAlertClient {
|
|||||||
return resp.getData().getChatId();
|
return resp.getData().getChatId();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送带错误信息的埋点报警消息
|
|
||||||
*
|
|
||||||
* @param chatId 会话ID
|
|
||||||
* @param buriedPointData 埋点数据
|
|
||||||
* @param failCount 失败数量
|
|
||||||
* @param errorMessage 错误信息
|
|
||||||
* @return 发送的消息ID
|
|
||||||
* @throws Exception 异常信息
|
|
||||||
*/
|
|
||||||
public String sendBuriedPointAlertMessage(String chatId, List<ChartImageGenerator.MonitoringDataPoint> buriedPointData, int failCount, String errorMessage) throws Exception {
|
|
||||||
HashMap<String, Object> templateData = new HashMap<>();
|
|
||||||
String imageKey = uploadImage(buriedPointData, errorMessage);
|
|
||||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
|
||||||
String currentTime = sdf.format(new Date());
|
|
||||||
templateData.put("alert_title", "埋点数据异常告警");
|
|
||||||
templateData.put("image_key", imageKey);
|
|
||||||
templateData.put("current_time", currentTime);
|
|
||||||
templateData.put("fail_count", failCount);
|
|
||||||
return sendCardMessage(chatId, "AAqdpjayeOVp2", templateData);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送带错误信息的埋点报警消息(创建群)
|
|
||||||
*
|
|
||||||
* @param buriedPointData 埋点数据
|
|
||||||
* @param failCount 失败数量
|
|
||||||
* @param errorMessage 错误信息
|
|
||||||
* @return 创建的群ID和消息ID,格式为 "chatId:messageId"
|
|
||||||
* @throws Exception 异常信息
|
|
||||||
*/
|
|
||||||
public String sendBuriedPointAlertMessage(List<ChartImageGenerator.MonitoringDataPoint> buriedPointData, int failCount, String errorMessage) throws Exception {
|
|
||||||
String chatId = createAlertChat();
|
|
||||||
String messageId = sendBuriedPointAlertMessage(chatId, buriedPointData, failCount, errorMessage);
|
|
||||||
return chatId + ":" + messageId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送报警消息
|
* 发送报警消息
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
com.tashow.cloud.sdk.feishu.config.LarkConfig
|
||||||
Reference in New Issue
Block a user