rabbitmq发送消息通用接口

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

在项目中我们经常会使用异步消息,然而每次发送消息都需要写很多重复性工作。


0. 项目结构:

在这里插入图片描述

一、公共类

提示:公共依赖的对象

代码如下

/**
 * 功能描述:
 * 〈〉
 *
 * @author : huan
 * @date   : 2022/11/1
 */
public interface BaseEventTypeEnum<E, V, T, X ,D> {

    E getEvent();

    X getExchange();

    T getType();

    V getServiceName();

    D getDesc();

}
import cn.hutool.extra.spring.SpringUtil;

/**
 * <p>描述:spring上下文工具类 </p>
 *
 * @author huan
 */
public class SpringContextUtils {

    /**
     * 获取指定的Bean
     * @param beanName
     * @param clz
     * @param <T>
     * @return
     */
    public static <T> T getBean(String beanName, Class<T> clz) {
        return SpringUtil.getBean(beanName, clz);
    }
}

二、消息体Bean对象

代码如下:

import lombok.Data;
import lombok.experimental.Accessors;

import java.io.Serializable;

/**
 * 功能描述: 消息体
 * 〈〉
 *
 * @author : huan
 * @date   : 2022/11/1
 */
@Data
@Accessors(chain = true)
public class EventMessageModel<T> implements Serializable {

    /** 业务id */
    private String businessId;

    /** 业务类型 上下架、更新信息 */
    private String businessType;

    /** 业务时间 */
    private Long businessTime;

    /** 业务数据 */
    private T businessData;
}
import lombok.Data;
import lombok.experimental.Accessors;

import java.io.Serializable;

/**
 * 功能描述: 事件对象
 * 〈〉
 *
 * @author : huan
 * @date   : 2022/10/31
 */
@Data
@Accessors(chain = true)
public class EventModel<T> implements Serializable {

    /** 事件id */
    private String eventId;

    /** 事件类型  */
    private String eventType;

    /** 时间 */
    private Long eventTime;

    /** 业务数据 */
    private T data;

}

三、定义构建消息体接口

提示:此方法用于构建发送消息的消息体

代码如下:

import cn.hutool.core.lang.UUID;
import com.saas.common.rabbitmq.enums.BaseEventTypeEnum;
import com.saas.common.rabbitmq.model.EventMessageModel;
import com.saas.common.rabbitmq.model.EventModel;

/**
 * 功能描述: 课程消息生产服务
 * 〈〉
 *
 * @author : huan
 * @date   : 2022/11/1
 */
public interface MessageProduceService<T, E> {

    /**
     * 功能描述: 前置处理
     * <p> 控制消息是否进行发送 </p>
     *
     * @param e
     * @return : Boolean
     * @author : huan
     * @date   : 2022/11/1
     */
    Boolean preHandle(E e);

    /**
     * 功能描述: 后置处理
     * <p> 消息发送异常后会调用此方法 </p>
     *
     * @param eventModel
     * @param typeEnum
     * @return : void
     * @author : huan
     * @date   : 2022/11/10
     */
    void postHandle(EventModel<EventMessageModel<T>> eventModel, BaseEventTypeEnum typeEnum);

    /**
     * 功能描述: 发送业务消息
     * 〈〉
     *
     * @param businessId
     * @param typeEnum
     * @return : void
     * @author : huan
     * @date   : 2022/11/1
     */
    EventModel<EventMessageModel<T>> buildEventModel(String businessId, BaseEventTypeEnum typeEnum);

    /**
     * 功能描述: 初始化-消息对象
     * <p> 无特殊要求:请不要重写此方法 </p>
     *
     * @param messageModel
     * @return : EventModel<EventMessageModel<T>>
     * @author : huan
     * @date   : 2022/11/1
     */
     default EventModel<EventMessageModel<T>> initModel(EventMessageModel<T> messageModel, BaseEventTypeEnum typeEnum) {
        return new EventModel<EventMessageModel<T>>()
                .setEventId(UUID.fastUUID().toString())
                .setEventType(typeEnum.getEvent().toString())
                .setEventTime(System.currentTimeMillis())
                .setData(messageModel);
    }

}

四、发送消息接口及实现类

提示:此方法是一个公共发送消息的接口

代码如下:

/**
 * 功能描述: 发送消息
 * 〈〉
 *
 * @author : huan
 * @date   : 2022/11/1
 */
public interface MessageSendService<T> {

    /**
     * 功能描述: 发送消息
     * 〈〉
     *
     * @param businessId    业务id
     * @param t             业务数据(用于拦截消息发送)
     * @param typeEnum      消息枚举
     * @return : void
     * @author : huan
     * @date   : 2022/11/2
     */
    void send(String businessId, T t, BaseEventTypeEnum typeEnum);

    /**
     * 功能描述: 发送消息
     * 〈〉
     *
     * @param businessId
     * @param typeEnum
     * @return : void
     * @author : huan
     * @date   : 2022/11/2
     */
    void send(String businessId, BaseEventTypeEnum typeEnum);
}

提示:此方法是一个公共发送消息的实现类

代码如下:

import cn.hutool.json.JSONUtil;
import com.saas.common.core.utils.SpringContextUtils;
import com.saas.common.rabbitmq.enums.BaseEventTypeEnum;
import com.saas.common.rabbitmq.model.EventMessageModel;
import com.saas.common.rabbitmq.model.EventModel;
import com.saas.common.rabbitmq.service.MessageProduceService;
import com.saas.common.rabbitmq.service.MessageSendService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Optional;

/**
 * 功能描述: 通用发送消息业务层
 * 〈〉
 *
 * @author : huan
 * @date   : 2022/11/1
 */
@Slf4j
@Component
public class MessageSendServiceImpl<T> implements MessageSendService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public void send(String businessId, Object obj, BaseEventTypeEnum typeEnum) {
        log.info("【消息】 发送参数 -> businessId:{}, obj:{}, typeEnum:{}", businessId, obj, typeEnum.getEvent());

        MessageProduceService produceService = getService(typeEnum.getServiceName().toString());
        log.info("【消息】 获取到的实例:{}", produceService.getClass().getName());

        // 是否允许发送
        Boolean sendFlag = produceService.preHandle(obj);
        if(!sendFlag) {
            log.warn("【消息】 禁止发送消息-> businessId:{}", businessId);
            return;
        }

        // 构建消息体
        EventModel eventModel = produceService.buildEventModel(businessId, typeEnum);

        // 发送消息
        Optional.ofNullable(eventModel).ifPresent(model -> {
            doSend(eventModel, typeEnum, produceService);
        });
    }

    @Override
    public void send(String businessId, BaseEventTypeEnum typeEnum) {
        send(businessId, null, typeEnum);
    }

    /**
     * 功能描述: 获取指定service
     * 〈〉
     *
     * @param beanName
     * @return : MessageProduceService
     * @author : huan
     * @date   : 2022/11/2
     */
    protected MessageProduceService getService(String beanName) {
        return SpringContextUtils.getBean(beanName, MessageProduceService.class);
    }

    /**
     * 功能描述: 发送消息幂等
     * 〈〉
     *
     * @param eventModel
     * @return : void
     * @author : huan
     * @date   : 2022/11/1
     */
    private void doSend(EventModel<EventMessageModel> eventModel, BaseEventTypeEnum typeEnum, MessageProduceService produceService) {
        String desc = typeEnum.getDesc().toString();
        String event = typeEnum.getEvent().toString();
        String exchange = typeEnum.getExchange().toString();

        try{
            String msg = JSONUtil.toJsonStr(eventModel);
            rabbitTemplate.convertAndSend(exchange, event, msg);
            log.info("【{}】异步发送成功, exchange:{}, routingKey:{}, msg:{}", desc, exchange, event, msg);
        }catch (Exception e){
            // 消息发送失败处理-- 比如使用redis保存发送失败的消息
            produceService.postHandle(eventModel, typeEnum);
            log.error("【{}】 -> eventId:{} 异步发送失败 error:{}", desc, eventModel.getEventId(), e.getMessage());
        }
    }
}

如果 MessageSendServiceImpl 是在公共jar中,需要使用spring.factories 将bean加载到spring容器中(在resources文件夹中创建META-INF文件夹,在META-INF创建spring.factories文件)

spring.factories文件内容如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.test.common.rabbitmq.service.impl.MessageSendServiceImpl

在这里插入图片描述

五、消息类型扩展

1.自定义ContentEventTypeEnum枚举

提示:不同的消息只需要实现 BaseEventTypeEnum,按ContentEventTypeEnum自定义即可

代码如下:

/**
 * 功能描述: 内容事件类型
 * 〈〉
 *
 * @author : huan
 * @date   : 2022/11/7
 */

@AllArgsConstructor
public enum ContentEventTypeEnum implements BaseEventTypeEnum {

    CREATE("content_add_event", "test_content","create","contentEventProducer","内容创建事件"),

    UPDATE_CONTENT("content_update_event", "test_content","update","contentEventProducer","内容修改事件"),
    UPDATE_SHELF("content_update_event", "test_content","shelf","contentEventProducer","内容上下架事件"),
    UPDATE_AUDIT("content_update_event", "test_content","audit","contentEventProducer","内容审核事件"),

    DELETE("content_update_event", "test_content","delete","contentEventProducer","内容删除事件"),
    ;

	/** 事件 */
    @Getter
    private String event;
	/** 交换机 */
    @Getter
    private String exchange;
	/** 事件类型 */
    @Getter
    private String type;
	/** 处理Bean的名称 */
    @Getter
    private String serviceName;
    /** 备注 */
    @Getter
    private String desc;
}

2.消息bean生产者

代码如下:


/**
 * 功能描述: 消息事件生产者
 * 〈〉
 *
 * @author : huan
 * @date   : 2022/11/8
 */
@Slf4j
@Component
public class ContentEventProducer implements MessageProduceService<TestBean, Boolean> {

    @Autowired
    private TestMapper testMapper;

    @Override
    public Boolean preHandle(Boolean flag) {
        return Optional.ofNullable(flag).orElse(Boolean.TRUE);
    }

    @Override
    public void postHandle(EventModel<EventMessageModel<TestBean>> eventModel, BaseEventTypeEnum typeEnum) {
        log.info("【内容】消息发送失败了!!!");
    }

    @Override
    public EventModel<EventMessageModel<TestBean>> buildEventModel(String businessId, BaseEventTypeEnum typeEnum) {
        // 获取业务数据
        TestBean testBean = testMapper.selectById(businessId);
        log.info("【内容】从库中查询到的信息:{}", cmsQuestion);
        if(!Optional.ofNullable(testBean).isPresent()) {
            log.error("【内容】从库中未查询到信息,终止消息发送 -> businessId:{}", businessId);
            return null;
        }

        Date businessDate = Optional.ofNullable(testBean.getUpdateTime())
                .orElse(testBean.getCreateTime());
        // 构建消息体
        EventMessageModel messageModel = new EventMessageModel<TestBean>()
                .setBusinessId(businessId)
                .setBusinessType(typeEnum.getType().toString())
                .setBusinessTime(businessDate.getTime())
                .setBusinessData(testBean);

        // 设置类型
        EventModel<EventMessageModel<TestBean>> eventModel = initModel(messageModel, typeEnum);

        log.info("【内容】发送的消息体:{}", eventModel);
        return eventModel;
    }
}

3.消息发送

代码如下:

	@Resource
    private MessageSendService sendService;

    // 异步消息 - 具体消息类型,通过枚举ContentEventTypeEnum控制
    sendService.send(test.getId().toString(), ContentEventTypeEnum.DELETE);

4.消息消费

代码如下:

/**
 * 功能描述: 接口消息处理
 * <p> ** 注意 ** </p>
 * <p> 测试消费消息 </p>
 *
 * @author : huan
 * @date   : 2022/11/2
 */
@Deprecated
@Slf4j
@Component
public class ContentEventConsumer {

    @RabbitListener(containerFactory = "simpleRabbitListenerContainerFactory",
            bindings = @QueueBinding(
                    // @Queue 属性value: 绑定的队列名称 - 自定义
                    value = @Queue(value = "test_content_update_queue", durable = "true"),
                    // @Exchange 属性name: 交换器名称
                    // 对应 ContentEventTypeEnum中的 exchange
                    exchange = @Exchange(name = "test_content", durable = "true", type = "topic"),
                    // key: 路由key, 也可以理解为topic, 需与发送端保持一致, 否则无法监听到消息
                    // 对应 ContentEventTypeEnum中的event
                    key = "content_update_event"
            ))
    public void handle(Channel channel, Message message, @Headers Map<String, Object> headers) {
        long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);

        try {
            String msg = new String(message.getBody(), StandardCharsets.UTF_8);

            log.info("【测试】消息消费成功: msg:{}", msg );
        }catch(Exception e){
            log.error("【测试】消息消费失败, errMsg:{}", e.getMessage());
            e.printStackTrace();
        }finally {
            try {
                channel.basicAck(deliveryTag,Boolean.FALSE);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

总结

  1. 通过实现BaseEventTypeEnum 来实现自定义的消息类型
  2. 消息生产时候,我们只需要关注消息体的构建 buildEventModel方法,控制是否发送消息通过preHandle方法, 消息发送失败后,通过postHandle方法处理
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值