31 changed files with 936 additions and 284 deletions
@ -0,0 +1,74 @@ |
|||
package com.bnyer.common.core.domain; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.IdType; |
|||
import com.baomidou.mybatisplus.annotation.TableField; |
|||
import com.baomidou.mybatisplus.annotation.TableId; |
|||
import com.bnyer.common.core.enums.EnumMessageStatus; |
|||
import com.fasterxml.jackson.annotation.JsonFormat; |
|||
import lombok.Data; |
|||
|
|||
import java.util.Date; |
|||
|
|||
/** |
|||
* @author :WXC |
|||
* @Date :2023/05/18 |
|||
* @description :消息记录公共实体 |
|||
*/ |
|||
@Data |
|||
public class BaseMqMessage { |
|||
|
|||
@TableId(value = "id", type = IdType.ASSIGN_ID) |
|||
private Long id; |
|||
|
|||
/** |
|||
* 消息key,作为redis的key,消费的时候判断是否存在, |
|||
* 存在就判断状态是否是已消费,如果不是就进行消费,如果是就过滤该消息 |
|||
*/ |
|||
@TableField(value = "message_key") |
|||
private String messageKey; |
|||
|
|||
/** |
|||
* 消息主题 |
|||
*/ |
|||
@TableField(value = "topic") |
|||
private String topic; |
|||
|
|||
/** |
|||
* 消息tag |
|||
*/ |
|||
@TableField(value = "tag") |
|||
private String tag; |
|||
|
|||
/** |
|||
* 消费组名称 |
|||
*/ |
|||
@TableField(value = "consumer_group_name") |
|||
private String consumerGroupName; |
|||
|
|||
/** |
|||
* 消息状态 |
|||
*/ |
|||
@TableField(value = "status") |
|||
private EnumMessageStatus status; |
|||
|
|||
/** |
|||
* 消息内容 |
|||
*/ |
|||
@TableField(value = "content") |
|||
private String content; |
|||
|
|||
/** |
|||
* 错误信息 |
|||
*/ |
|||
@TableField(value = "err_msg") |
|||
private String errMsg; |
|||
|
|||
/** |
|||
* 创建时间 |
|||
*/ |
|||
@TableField(value = "create_time") |
|||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
|||
private Date createTime; |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
package com.bnyer.common.core.domain; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import lombok.Getter; |
|||
import lombok.NoArgsConstructor; |
|||
import lombok.Setter; |
|||
|
|||
/** |
|||
* @author :WXC |
|||
* @Date :2023/05/18 |
|||
* @description :订单服务本地消息表 |
|||
*/ |
|||
@Getter |
|||
@Setter |
|||
@NoArgsConstructor |
|||
@TableName(value = "order_mq_message_record") |
|||
public class OrderMqMessageRecord extends BaseMqMessage{ |
|||
|
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
package com.bnyer.common.core.enums; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Getter; |
|||
|
|||
/** |
|||
* @author :WXC |
|||
* @Date :2023/05/18 |
|||
* @description :消息状态 |
|||
*/ |
|||
@Getter |
|||
@AllArgsConstructor |
|||
public enum EnumMessageStatus { |
|||
//处理中
|
|||
PROCESS, |
|||
//成功
|
|||
SUCCESS, |
|||
//失败,
|
|||
FAILS, |
|||
//作废
|
|||
INVALID, |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
package com.bnyer.common.rocketmq.config; |
|||
|
|||
|
|||
import com.bnyer.common.rocketmq.persist.IPersist; |
|||
import com.bnyer.common.rocketmq.persist.RedisPersist; |
|||
import lombok.Getter; |
|||
import lombok.ToString; |
|||
import org.springframework.data.redis.core.StringRedisTemplate; |
|||
|
|||
/** |
|||
* @author :WXC |
|||
* @Date :2023/05/19 |
|||
* @description :重复消费配置 |
|||
*/ |
|||
@Getter |
|||
@ToString |
|||
public class RepeatConsumerConfig { |
|||
|
|||
/** |
|||
* 不启用去重 |
|||
*/ |
|||
public static final int REPEAT_STRATEGY_DISABLE = 0; |
|||
/** |
|||
* 开启去重,发现有处理中的消息,后面再重试 |
|||
*/ |
|||
public static final int REPEAT_STRATEGY_CONSUME_LATER = 1; |
|||
/** |
|||
* 用以标记去重的时候是哪个应用消费的,同一个应用才需要去重 |
|||
*/ |
|||
private final String applicationName; |
|||
|
|||
private IPersist persist; |
|||
|
|||
|
|||
/** |
|||
* 去重策略,默认不去重 |
|||
*/ |
|||
private int repeatStrategy = REPEAT_STRATEGY_DISABLE; |
|||
|
|||
|
|||
/** |
|||
* 对于消费中的消息,多少毫秒内认为重复,默认一分钟,即一分钟内的重复消息都会串行处理(等待前一个消息消费成功/失败),超过这个时间如果消息还在消费就不认为重复了(为了防止消息丢失) |
|||
*/ |
|||
private long processingExpireMilliSeconds = 60 * 1000; |
|||
|
|||
/** |
|||
* 消息消费成功后,记录保留多少分钟,默认一天,即一天内的消息不会重复 |
|||
*/ |
|||
private long recordReserveMinutes = 60 * 24; |
|||
|
|||
private RepeatConsumerConfig(String applicationName, int repeatStrategy, StringRedisTemplate redisTemplate) { |
|||
if (redisTemplate !=null) { |
|||
this.persist = new RedisPersist(redisTemplate); |
|||
} |
|||
this.repeatStrategy = repeatStrategy; |
|||
this.applicationName = applicationName; |
|||
} |
|||
|
|||
private RepeatConsumerConfig(String applicationName) { |
|||
this.repeatStrategy = REPEAT_STRATEGY_DISABLE; |
|||
this.applicationName = applicationName; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 利用redis去重 |
|||
* @param applicationName |
|||
* @param redisTemplate |
|||
* @return |
|||
*/ |
|||
public static RepeatConsumerConfig enableRedisConfig(String applicationName, StringRedisTemplate redisTemplate) { |
|||
return new RepeatConsumerConfig(applicationName, REPEAT_STRATEGY_CONSUME_LATER, redisTemplate); |
|||
} |
|||
|
|||
|
|||
public static RepeatConsumerConfig disableConfig(String applicationName) { |
|||
return new RepeatConsumerConfig(applicationName); |
|||
} |
|||
|
|||
|
|||
|
|||
public void setProcessingExpireMilliSeconds(long processingExpireMilliSeconds) { |
|||
this.processingExpireMilliSeconds = processingExpireMilliSeconds; |
|||
} |
|||
|
|||
public void setRecordReserveMinutes(long recordReserveMinutes) { |
|||
this.recordReserveMinutes = recordReserveMinutes; |
|||
} |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
package com.bnyer.common.rocketmq.constant; |
|||
|
|||
/** |
|||
* @author :WXC |
|||
* @Date :2023/05/20 |
|||
* @description : |
|||
*/ |
|||
public class RocketMqRepeatConstant { |
|||
/** |
|||
* 消费中 |
|||
*/ |
|||
public final static String CONSUME_STATUS_CONSUMING = "CONSUME_ING"; |
|||
/** |
|||
* 消费成功 |
|||
*/ |
|||
public final static String CONSUME_STATUS_SUCCESS = "CONSUME_SUCCESS"; |
|||
|
|||
/** |
|||
* 重复消息redis前缀 |
|||
*/ |
|||
public final static String REPEAT_REDIS_KEY_PREFIX = "RepeatMessage:"; |
|||
|
|||
} |
|||
@ -1,84 +0,0 @@ |
|||
package com.bnyer.common.rocketmq.domain; |
|||
|
|||
import com.alibaba.fastjson.JSON; |
|||
import com.bnyer.common.rocketmq.enums.EnumMessageStatus; |
|||
import lombok.Builder; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
import org.apache.rocketmq.client.producer.SendResult; |
|||
import org.apache.rocketmq.client.producer.SendStatus; |
|||
|
|||
import java.time.LocalDateTime; |
|||
import java.util.Objects; |
|||
|
|||
/** |
|||
* @author :WXC |
|||
* @Date :2023/05/18 |
|||
* @description :消息日志 |
|||
*/ |
|||
@Getter |
|||
@Setter |
|||
@Builder |
|||
public class MessageLog <T extends BaseMessage>{ |
|||
|
|||
/** |
|||
* 消息key,作为redis的key,消费的时候判断是否存在, |
|||
* 存在就判断状态是否是已消费,如果不是就进行消费,如果是就过滤该消息 |
|||
*/ |
|||
private String messageKey; |
|||
|
|||
/** |
|||
* 消息主题 |
|||
*/ |
|||
private String topic; |
|||
|
|||
/** |
|||
* 消息tag |
|||
*/ |
|||
private String tag; |
|||
|
|||
/** |
|||
* 消费组名称 |
|||
*/ |
|||
private String consumerGroupName; |
|||
|
|||
/** |
|||
* 消息状态 |
|||
*/ |
|||
private EnumMessageStatus status; |
|||
|
|||
/** |
|||
* 错误信息 |
|||
*/ |
|||
private String errMsg; |
|||
|
|||
/** |
|||
* 消费成功时间 |
|||
*/ |
|||
private LocalDateTime consumerTime; |
|||
|
|||
/** |
|||
* 消息内容 |
|||
*/ |
|||
private String message; |
|||
|
|||
/** |
|||
* 重试次数,用于判断重试次数,超过最大重试次数就人工补偿 |
|||
*/ |
|||
protected Integer retryTimes = 0; |
|||
|
|||
public MessageLog(T message, SendResult sendResult) { |
|||
this.messageKey = message.getKey(); |
|||
this.message = JSON.toJSONString(message); |
|||
if (Objects.isNull(sendResult)){ |
|||
this.status = EnumMessageStatus.WALT_SEND; |
|||
}else { |
|||
if (SendStatus.SEND_OK == sendResult.getSendStatus()){ |
|||
this.status = EnumMessageStatus.SEND_OK; |
|||
}else { |
|||
this.status = EnumMessageStatus.SEND_FAILS; |
|||
} |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
package com.bnyer.common.rocketmq.domain; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Getter; |
|||
import lombok.ToString; |
|||
|
|||
/** |
|||
* @author :WXC |
|||
* @Date :2023/05/20 |
|||
* @description : 重复消息元素 |
|||
*/ |
|||
@AllArgsConstructor |
|||
@Getter |
|||
@ToString |
|||
public class RepeatElement { |
|||
/** |
|||
* 消费的应用 |
|||
*/ |
|||
private String applicationName; |
|||
/** |
|||
* 唯一key |
|||
*/ |
|||
private String messageKey; |
|||
/** |
|||
* 消费组 |
|||
*/ |
|||
private String consumerGroupName; |
|||
/** |
|||
* 主题 |
|||
*/ |
|||
private String topic; |
|||
/** |
|||
* 标签 |
|||
*/ |
|||
private String tag; |
|||
|
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
package com.bnyer.common.rocketmq.domain.order; |
|||
|
|||
import com.bnyer.common.core.enums.EnumMessageStatus; |
|||
import com.bnyer.common.rocketmq.domain.BaseMessage; |
|||
import com.fasterxml.jackson.annotation.JsonFormat; |
|||
import lombok.Getter; |
|||
import lombok.NoArgsConstructor; |
|||
import lombok.Setter; |
|||
|
|||
import java.util.Date; |
|||
|
|||
/** |
|||
* @author :WXC |
|||
* @Date :2023/05/18 |
|||
* @description :订单服务本地消息表记录 |
|||
*/ |
|||
@Getter |
|||
@Setter |
|||
@NoArgsConstructor |
|||
public class OrderMqLocalRecordMessage extends BaseMessage { |
|||
private Long id; |
|||
|
|||
/** |
|||
* 消息状态 |
|||
*/ |
|||
private EnumMessageStatus status; |
|||
|
|||
/** |
|||
* 消息内容 |
|||
*/ |
|||
private String content; |
|||
|
|||
/** |
|||
* 创建时间 |
|||
*/ |
|||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
|||
private Date createTime; |
|||
|
|||
} |
|||
@ -1,28 +0,0 @@ |
|||
package com.bnyer.common.rocketmq.enums; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Getter; |
|||
|
|||
/** |
|||
* @author :WXC |
|||
* @Date :2023/05/18 |
|||
* @description :消息状态 |
|||
*/ |
|||
@Getter |
|||
@AllArgsConstructor |
|||
public enum EnumMessageStatus { |
|||
//待发送
|
|||
WALT_SEND, |
|||
//发送成功
|
|||
SEND_OK, |
|||
//发送失败
|
|||
SEND_FAILS, |
|||
//待消费
|
|||
WALT_CONSUMER, |
|||
//消费中
|
|||
CONSUMER_PROCESS, |
|||
//消费成功
|
|||
CONSUMER_OK, |
|||
//消费失败
|
|||
CONSUMER_FAILS |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
package com.bnyer.common.rocketmq.persist; |
|||
|
|||
|
|||
import com.bnyer.common.rocketmq.domain.BaseMessage; |
|||
import com.bnyer.common.rocketmq.domain.RepeatElement; |
|||
|
|||
/** |
|||
* @author :WXC |
|||
* @Date :2023/05/20 |
|||
* @description : 持久层处理重复消息,目前支持redis,可扩展mysql |
|||
*/ |
|||
public interface IPersist { |
|||
|
|||
/** |
|||
* 设置正在消费的消息到存储中 |
|||
* @param repeatElement |
|||
* @param processingExpireMilliSeconds |
|||
* @return |
|||
* @param <T> |
|||
*/ |
|||
<T extends BaseMessage> boolean setConsumingIfNX(RepeatElement repeatElement, long processingExpireMilliSeconds); |
|||
|
|||
/** |
|||
* 删除存储中的消息 |
|||
* @param repeatElement |
|||
* @param <T> |
|||
*/ |
|||
<T extends BaseMessage> void delete(RepeatElement repeatElement); |
|||
|
|||
/** |
|||
* 修改存储中的消息 |
|||
* @param repeatElement |
|||
* @param recordReserveMinutes |
|||
* @param <T> |
|||
*/ |
|||
<T extends BaseMessage> void markConsumed(RepeatElement repeatElement, long recordReserveMinutes); |
|||
|
|||
/** |
|||
* 获取存储中的消息 |
|||
* @param repeatElement |
|||
* @return |
|||
* @param <T> |
|||
*/ |
|||
<T extends BaseMessage> String get(RepeatElement repeatElement); |
|||
|
|||
default <T extends BaseMessage> String toPrintInfo(RepeatElement repeatElement) { |
|||
return repeatElement.toString(); |
|||
} |
|||
} |
|||
@ -0,0 +1,81 @@ |
|||
package com.bnyer.common.rocketmq.persist; |
|||
|
|||
|
|||
import com.bnyer.common.rocketmq.domain.BaseMessage; |
|||
import com.bnyer.common.rocketmq.domain.RepeatElement; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.data.redis.connection.RedisStringCommands; |
|||
import org.springframework.data.redis.core.RedisCallback; |
|||
import org.springframework.data.redis.core.StringRedisTemplate; |
|||
import org.springframework.data.redis.core.types.Expiration; |
|||
|
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
import static com.bnyer.common.rocketmq.constant.RocketMqRepeatConstant.*; |
|||
|
|||
/** |
|||
* @author :WXC |
|||
* @Date :2023/05/20 |
|||
* @description : redis持久层 |
|||
*/ |
|||
public class RedisPersist implements IPersist { |
|||
|
|||
private final StringRedisTemplate redisTemplate; |
|||
|
|||
public RedisPersist(StringRedisTemplate redisTemplate) { |
|||
if (redisTemplate == null) { |
|||
throw new NullPointerException("redis template is null"); |
|||
} |
|||
this.redisTemplate = redisTemplate; |
|||
} |
|||
|
|||
@Override |
|||
public <T extends BaseMessage> boolean setConsumingIfNX(RepeatElement repeatElement, long dedupProcessingExpireMilliSeconds) { |
|||
String repeatKey = buildRepeatMessageRedisKey(repeatElement.getApplicationName(), repeatElement.getTopic(), repeatElement.getTag(), repeatElement.getMessageKey()); |
|||
//setnx, 成功就可以消费
|
|||
Boolean execute = redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> redisConnection.set(repeatKey.getBytes(), |
|||
(CONSUME_STATUS_CONSUMING).getBytes(), Expiration.milliseconds(dedupProcessingExpireMilliSeconds), RedisStringCommands.SetOption.SET_IF_ABSENT)); |
|||
if (execute == null) { |
|||
return false; |
|||
} |
|||
return execute; |
|||
} |
|||
|
|||
@Override |
|||
public <T extends BaseMessage> void delete(RepeatElement repeatElement) { |
|||
String repeatKey = buildRepeatMessageRedisKey(repeatElement.getApplicationName(), repeatElement.getTopic(), repeatElement.getTag(), repeatElement.getMessageKey()); |
|||
redisTemplate.delete(repeatKey); |
|||
} |
|||
|
|||
@Override |
|||
public <T extends BaseMessage> void markConsumed(RepeatElement repeatElement, long dedupRecordReserveMinutes) { |
|||
String repeatKey = buildRepeatMessageRedisKey(repeatElement.getApplicationName(), repeatElement.getTopic(), repeatElement.getTag(), repeatElement.getMessageKey()); |
|||
redisTemplate.opsForValue().set(repeatKey, CONSUME_STATUS_SUCCESS, dedupRecordReserveMinutes, TimeUnit.MINUTES); |
|||
} |
|||
|
|||
@Override |
|||
public <T extends BaseMessage> String get(RepeatElement repeatElement) { |
|||
String repeatKey = buildRepeatMessageRedisKey(repeatElement.getApplicationName(), repeatElement.getTopic(), repeatElement.getTag(), repeatElement.getMessageKey()); |
|||
return redisTemplate.opsForValue().get(repeatKey); |
|||
} |
|||
|
|||
@Override |
|||
public <T extends BaseMessage> String toPrintInfo(RepeatElement repeatElement) { |
|||
return buildRepeatMessageRedisKey(repeatElement.getApplicationName(), repeatElement.getTopic(), repeatElement.getTag(), repeatElement.getMessageKey()); |
|||
} |
|||
|
|||
/** |
|||
* 构建重复消息rediskey |
|||
* @param applicationName |
|||
* @param topic |
|||
* @param tag |
|||
* @param messageKey |
|||
* @return |
|||
*/ |
|||
private String buildRepeatMessageRedisKey(String applicationName, String topic, String tag, String messageKey) { |
|||
String prefix = REPEAT_REDIS_KEY_PREFIX + applicationName + ":" + topic + (StringUtils.isNotEmpty(tag) ? ":"+ tag :""); |
|||
return prefix + ":" + messageKey; |
|||
} |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
package com.bnyer.common.rocketmq.strategy; |
|||
|
|||
|
|||
import com.bnyer.common.rocketmq.domain.BaseMessage; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
/** |
|||
* @author :WXC |
|||
* @Date :2023/05/20 |
|||
* @description : |
|||
*/ |
|||
@Slf4j |
|||
public class NormalRepeatStrategy implements RepeatConsumerStrategy { |
|||
|
|||
@Override |
|||
public <T extends BaseMessage> boolean invoke(T message) { |
|||
return false; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
package com.bnyer.common.rocketmq.strategy; |
|||
|
|||
|
|||
import com.bnyer.common.rocketmq.config.RepeatConsumerConfig; |
|||
import com.bnyer.common.rocketmq.domain.BaseMessage; |
|||
import com.bnyer.common.rocketmq.domain.RepeatElement; |
|||
import com.bnyer.common.rocketmq.persist.IPersist; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
|
|||
import java.util.function.Function; |
|||
|
|||
|
|||
/** |
|||
* @author :WXC |
|||
* @Date :2023/05/20 |
|||
* @description :如果已经消费过,则直接消费幂等掉 |
|||
*/ |
|||
@Slf4j |
|||
public class RedisRepeatStrategy implements RepeatConsumerStrategy { |
|||
|
|||
private final RepeatConsumerConfig filterConfig; |
|||
|
|||
/** |
|||
* 获取去重键的函数 |
|||
*/ |
|||
private final Function<BaseMessage, String> repeatMessageKeyFunction; |
|||
|
|||
public RedisRepeatStrategy(RepeatConsumerConfig dedupConfig, Function<BaseMessage, String> dedupMessageKeyFunction) { |
|||
this.filterConfig = dedupConfig; |
|||
this.repeatMessageKeyFunction = dedupMessageKeyFunction; |
|||
} |
|||
|
|||
|
|||
@Override |
|||
public <T extends BaseMessage> boolean invoke(T message) { |
|||
return doInvoke(message); |
|||
} |
|||
|
|||
private <T extends BaseMessage> boolean doInvoke(T message) { |
|||
IPersist persist = filterConfig.getPersist(); |
|||
RepeatElement repeatElement = new RepeatElement(filterConfig.getApplicationName(), message.getConsumerGroupName(),message.getTopic() |
|||
, message.getTag()==null ? "" : message.getTag() |
|||
, repeatMessageKeyFunction.apply(message)); |
|||
boolean shouldConsume = true; |
|||
if (StringUtils.isNotBlank(repeatElement.getMessageKey())) { |
|||
shouldConsume = persist.setConsumingIfNX(repeatElement, filterConfig.getProcessingExpireMilliSeconds()); |
|||
} |
|||
//设置成功,证明没有消费过
|
|||
if (shouldConsume) { |
|||
return false; |
|||
} else { |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
package com.bnyer.common.rocketmq.strategy; |
|||
|
|||
|
|||
import com.bnyer.common.rocketmq.domain.BaseMessage; |
|||
|
|||
/** |
|||
* @author :WXC |
|||
* @Date :2023/05/20 |
|||
* @description : 重复消费策略,目前支持redis,可扩展mysql方式 |
|||
*/ |
|||
public interface RepeatConsumerStrategy { |
|||
|
|||
<T extends BaseMessage> boolean invoke(T message); |
|||
|
|||
} |
|||
|
|||
@ -0,0 +1,61 @@ |
|||
package com.bnyer.order.listener; |
|||
|
|||
import com.bnyer.common.core.domain.OrderMqMessageRecord; |
|||
import com.bnyer.common.core.utils.bean.EntityConvertUtil; |
|||
import com.bnyer.common.rocketmq.constant.RocketMqTopic; |
|||
import com.bnyer.common.rocketmq.domain.order.OrderMqLocalRecordMessage; |
|||
import com.bnyer.common.rocketmq.handle.EnhanceMessageHandler; |
|||
import com.bnyer.order.service.OrderMqMessageRecordService; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; |
|||
import org.apache.rocketmq.spring.core.RocketMQListener; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
import javax.annotation.Resource; |
|||
|
|||
/** |
|||
* @author :WXC |
|||
* @Date :2023/05/20 |
|||
* @description : |
|||
*/ |
|||
@Slf4j |
|||
@Component |
|||
@RocketMQMessageListener(topic = RocketMqTopic.ORDER_RETURN_MSG_TOPIC,consumerGroup = RocketMqTopic.ORDER_RETURN_MSG_TOPIC) |
|||
public class OrderReturnMessageConsumer extends EnhanceMessageHandler<OrderMqLocalRecordMessage> implements RocketMQListener<OrderMqLocalRecordMessage> { |
|||
|
|||
@Resource |
|||
private OrderMqMessageRecordService orderMqMessageRecordService; |
|||
|
|||
@Override |
|||
public void onMessage(OrderMqLocalRecordMessage message) { |
|||
super.dispatchMessage(message); |
|||
OrderMqMessageRecord orderMqMessageRecord = EntityConvertUtil.copy(message, OrderMqMessageRecord.class); |
|||
orderMqMessageRecordService.editMessageRecord(orderMqMessageRecord); |
|||
} |
|||
|
|||
@Override |
|||
protected void handleMessage(OrderMqLocalRecordMessage message) throws Exception { |
|||
|
|||
} |
|||
|
|||
@Override |
|||
protected void handleMaxRetriesExceeded(OrderMqLocalRecordMessage message) { |
|||
|
|||
} |
|||
|
|||
@Override |
|||
protected boolean filter(OrderMqLocalRecordMessage message) { |
|||
return super.handleMsgRepeat(message); |
|||
} |
|||
|
|||
@Override |
|||
protected boolean isRetry() { |
|||
return true; |
|||
} |
|||
|
|||
@Override |
|||
protected boolean throwException() { |
|||
return false; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
package com.bnyer.order.mapper; |
|||
|
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
import com.bnyer.common.core.domain.OrderMqMessageRecord; |
|||
import org.apache.ibatis.annotations.Mapper; |
|||
|
|||
/** |
|||
* @author :WXC |
|||
* @description : |
|||
*/ |
|||
@Mapper |
|||
public interface OrderMqMessageRecordMapper extends BaseMapper<OrderMqMessageRecord> { |
|||
|
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
package com.bnyer.order.service; |
|||
|
|||
import com.bnyer.common.core.domain.OrderMqMessageRecord; |
|||
import com.bnyer.common.rocketmq.domain.BaseMessage; |
|||
|
|||
/** |
|||
* @author :WXC |
|||
* @Date :2023/05/20 |
|||
* @description :订单服务本地消息service层 |
|||
*/ |
|||
public interface OrderMqMessageRecordService { |
|||
|
|||
/** |
|||
* 发送异步消息 |
|||
* @param topic |
|||
* @param tag |
|||
* @param addUserVipRecordMessage |
|||
* @param <T> |
|||
*/ |
|||
<T> void sendAsyncMsg(String topic, String tag, T addUserVipRecordMessage); |
|||
|
|||
/** |
|||
* 发送异步延时消息 |
|||
* @param topic |
|||
* @param tag |
|||
* @param message |
|||
* @param delayLevel |
|||
* @param <T> |
|||
*/ |
|||
<T> void sendAsyncMsg(String topic, String tag, T message, int delayLevel); |
|||
|
|||
/** |
|||
* 保存消息记录 |
|||
* @param topic |
|||
* @param tag |
|||
* @param message |
|||
* @return |
|||
* @param <T> |
|||
*/ |
|||
<T> OrderMqMessageRecord saveMessageRecord(String topic, String tag, T message); |
|||
|
|||
/** |
|||
* 修改消息记录 |
|||
* @param orderMqMessageRecord |
|||
*/ |
|||
void editMessageRecord(OrderMqMessageRecord orderMqMessageRecord); |
|||
|
|||
} |
|||
@ -0,0 +1,132 @@ |
|||
package com.bnyer.order.service.impl; |
|||
|
|||
import com.alibaba.fastjson.JSON; |
|||
import com.bnyer.common.core.constant.ServiceNameConstants; |
|||
import com.bnyer.common.core.domain.OrderMqMessageRecord; |
|||
import com.bnyer.common.core.enums.EnumMessageStatus; |
|||
import com.bnyer.common.core.utils.bean.EntityConvertUtil; |
|||
import com.bnyer.common.core.utils.uuid.IdUtils; |
|||
import com.bnyer.common.rocketmq.domain.order.OrderMqLocalRecordMessage; |
|||
import com.bnyer.common.rocketmq.template.RocketMQEnhanceTemplate; |
|||
import com.bnyer.order.mapper.OrderMqMessageRecordMapper; |
|||
import com.bnyer.order.service.OrderMqMessageRecordService; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.rocketmq.client.producer.SendCallback; |
|||
import org.apache.rocketmq.client.producer.SendResult; |
|||
import org.apache.rocketmq.spring.core.RocketMQTemplate; |
|||
import org.springframework.messaging.support.MessageBuilder; |
|||
import org.springframework.stereotype.Service; |
|||
import org.springframework.transaction.annotation.Transactional; |
|||
|
|||
import javax.annotation.Resource; |
|||
import java.util.Date; |
|||
|
|||
/** |
|||
* @author :WXC |
|||
* @Date :2023/05/19 |
|||
* @description : |
|||
*/ |
|||
@Slf4j |
|||
@Service |
|||
public class OrderMqMessageRecordServiceImpl implements OrderMqMessageRecordService { |
|||
|
|||
/** |
|||
* 发送超时时间 |
|||
*/ |
|||
private final static int TIME_OUT = 10000; |
|||
|
|||
@Resource |
|||
private RocketMQEnhanceTemplate rocketMQEnhanceTemplate; |
|||
|
|||
@Resource |
|||
private OrderMqMessageRecordMapper orderMqMessageRecordMapper; |
|||
|
|||
/** |
|||
* 发送异步消息(通过线程池执行发送到broker的消息任务,执行完后回调:在SendCallback中可处理相关成功失败时的逻辑) |
|||
* (适合对响应时间敏感的业务场景) |
|||
*/ |
|||
@Transactional |
|||
@Override |
|||
public <T> void sendAsyncMsg(String topic, String tag, T message) { |
|||
RocketMQTemplate rocketMQTemplate = rocketMQEnhanceTemplate.getTemplate(); |
|||
//保存消息记录
|
|||
log.info("消息发送中,开始入库本地消息记录表"); |
|||
OrderMqMessageRecord orderMqMessageRecord = saveMessageRecord(topic,tag, message); |
|||
//发消息
|
|||
OrderMqLocalRecordMessage mqLocalMessage = EntityConvertUtil.copy(orderMqMessageRecord, OrderMqLocalRecordMessage.class); |
|||
mqLocalMessage.setSource(ServiceNameConstants.ORDER_SERVICE); |
|||
rocketMQTemplate.asyncSend(rocketMQEnhanceTemplate.buildDestination(topic,tag), MessageBuilder.withPayload(mqLocalMessage).build(), new SendCallback() { |
|||
@Override |
|||
public void onSuccess(SendResult sendResult) { |
|||
log.info("消息发送成功,topic:{},tag:{},result:{}",topic,tag, JSON.toJSONString(sendResult)); |
|||
} |
|||
@Override |
|||
public void onException(Throwable throwable) { |
|||
log.error("消息发送失败,topic:{},tag:{},error:{}",topic,tag,throwable.getMessage()); |
|||
log.info("消息发送失败,更新消息记录,等待定时任务进行消息补偿"); |
|||
orderMqMessageRecord.setErrMsg(throwable.getMessage()); |
|||
editMessageRecord(orderMqMessageRecord); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 发送异步延时消息(通过线程池执行发送到broker的消息任务,执行完后回调:在SendCallback中可处理相关成功失败时的逻辑) |
|||
* (适合对响应时间敏感的业务场景) |
|||
*/ |
|||
@Transactional |
|||
@Override |
|||
public <T> void sendAsyncMsg(String topic, String tag, T message, int delayLevel) { |
|||
RocketMQTemplate rocketMQTemplate = rocketMQEnhanceTemplate.getTemplate(); |
|||
//保存消息记录
|
|||
log.info("消息发送中,开始入库本地消息记录表"); |
|||
OrderMqMessageRecord orderMqMessageRecord = saveMessageRecord(topic,tag, message); |
|||
//发消息
|
|||
OrderMqLocalRecordMessage mqLocalMessage = EntityConvertUtil.copy(orderMqMessageRecord, OrderMqLocalRecordMessage.class); |
|||
mqLocalMessage.setRetryTimes(0); |
|||
mqLocalMessage.setSource(ServiceNameConstants.ORDER_SERVICE); |
|||
rocketMQTemplate.asyncSend(rocketMQEnhanceTemplate.buildDestination(topic,tag), MessageBuilder.withPayload(mqLocalMessage).build(), new SendCallback() { |
|||
@Override |
|||
public void onSuccess(SendResult sendResult) { |
|||
log.info("消息发送成功,topic:{},tag:{},result:{}",topic,tag,JSON.toJSONString(sendResult)); |
|||
} |
|||
@Override |
|||
public void onException(Throwable throwable) { |
|||
log.error("消息发送失败,topic:{},tag:{},error:{}",topic,tag,throwable.getMessage()); |
|||
log.info("消息发送失败,更新消息记录,等待定时任务进行消息补偿"); |
|||
orderMqMessageRecord.setErrMsg(throwable.getMessage()); |
|||
editMessageRecord(orderMqMessageRecord); |
|||
} |
|||
},TIME_OUT,delayLevel); |
|||
} |
|||
|
|||
/** |
|||
* 添加消息记录 |
|||
* @param message |
|||
* @param <T> |
|||
*/ |
|||
@Transactional |
|||
public <T> OrderMqMessageRecord saveMessageRecord(String topic, String tag, T message){ |
|||
RocketMQTemplate rocketMQTemplate = rocketMQEnhanceTemplate.getTemplate(); |
|||
String consumerGroup = rocketMQTemplate.getConsumer().getConsumerGroup(); |
|||
OrderMqMessageRecord orderMqMessageRecord = new OrderMqMessageRecord(); |
|||
orderMqMessageRecord.setStatus(EnumMessageStatus.PROCESS); |
|||
orderMqMessageRecord.setConsumerGroupName(consumerGroup); |
|||
orderMqMessageRecord.setTopic(topic); |
|||
orderMqMessageRecord.setTag(tag); |
|||
orderMqMessageRecord.setCreateTime(new Date()); |
|||
orderMqMessageRecord.setMessageKey(IdUtils.randomUUID()); |
|||
orderMqMessageRecordMapper.insert(orderMqMessageRecord); |
|||
return orderMqMessageRecord; |
|||
} |
|||
|
|||
/** |
|||
* 修改消息记录 |
|||
* @param orderMqMessageRecord |
|||
*/ |
|||
@Transactional |
|||
public void editMessageRecord(OrderMqMessageRecord orderMqMessageRecord){ |
|||
orderMqMessageRecordMapper.updateById(orderMqMessageRecord); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|||
<mapper namespace="com.bnyer.order.mapper.OrderMqMessageRecordMapper"> |
|||
<resultMap id="BaseResultMap" type="com.bnyer.common.core.domain.OrderMqMessageRecord"> |
|||
<!--order_mq_message--> |
|||
<id column="id" jdbcType="BIGINT" property="id" /> |
|||
<result column="message_key" jdbcType="VARCHAR" property="messageKey" /> |
|||
<result column="topic" jdbcType="VARCHAR" property="topic" /> |
|||
<result column="tag" jdbcType="VARCHAR" property="tag" /> |
|||
<result column="consumer_group_name" jdbcType="VARCHAR" property="consumerGroupName" /> |
|||
<result column="status" jdbcType="VARCHAR" property="status" /> |
|||
<result column="err_msg" jdbcType="VARCHAR" property="errMsg" /> |
|||
<result column="content" jdbcType="VARCHAR" property="content" /> |
|||
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" /> |
|||
</resultMap> |
|||
|
|||
<sql id="Base_Column_List"> |
|||
t.id, |
|||
t.message_key, |
|||
t.topic, |
|||
t.tag, |
|||
t.consumer_group_name, |
|||
t.status, |
|||
t.err_msg, |
|||
t.content, |
|||
t.create_time, |
|||
</sql> |
|||
|
|||
</mapper> |
|||
Loading…
Reference in new issue