代码选自jeepay
controller层:
@RestController
@RequestMapping("/api/mchNotify")
public class MchNotifyController extends CommonCtrl {
@Autowired private MchNotifyRecordService mchNotifyService;
@Autowired private IMQSender mqSender;
@PreAuthorize("hasAuthority('ENT_MCH_NOTIFY_RESEND')")
@RequestMapping(value="resend/{notifyId}", method = RequestMethod.POST)
public ApiRes resend(@PathVariable("notifyId") Long notifyId) {
MchNotifyRecord mchNotify = mchNotifyService.getById(notifyId);
if (mchNotify == null) {
return ApiRes.fail(ApiCodeEnum.SYS_OPERATION_FAIL_SELETE);
}
if (mchNotify.getState() != MchNotifyRecord.STATE_FAIL) {
throw new BizException("请选择失败的通知记录");
}
//更新通知中
mchNotifyService.getBaseMapper().updateIngAndAddNotifyCountLimit(notifyId);
//调起MQ重发
mqSender.send(PayOrderMchNotifyMQ.build(notifyId));
return ApiRes.ok(mchNotify);
}
}
MQ 消息发送器 接口
public interface IMQSender {
/** 推送MQ消息, 实时 **/
void send(AbstractMQ mqModel);
/** 推送MQ消息, 延迟接收,单位:s **/
void send(AbstractMQ mqModel, int delay);
}
有三个实现类,根据自己使用的mq类型来选择:
这里看rabbitmq的:
@Component
@ConditionalOnProperty(name = MQVenderCS.YML_VENDER_KEY, havingValue = MQVenderCS.RABBIT_MQ)
//在spring boot中有时候需要控制配置类是否生效,可以使用@ConditionalOnProperty注解来控制@Configuration是否生效.
public class RabbitMQSender implements IMQSender {
@Autowired
private RabbitTemplate rabbitTemplate;
@Override
public void send(AbstractMQ mqModel) {
if(mqModel.getMQType() == MQSendTypeEnum.QUEUE){
rabbitTemplate.convertAndSend(mqModel.getMQName(), mqModel.toMessage());
}else{
// fanout模式 的 routeKEY 没意义。
this.rabbitTemplate.convertAndSend(RabbitMQConfig.FANOUT_EXCHANGE_NAME_PREFIX + mqModel.getMQName(), null, mqModel.toMessage());
}
}
@Override
public void send(AbstractMQ mqModel, int delay) {
if(mqModel.getMQType() == MQSendTypeEnum.QUEUE){
rabbitTemplate.convertAndSend(RabbitMQConfig.DELAYED_EXCHANGE_NAME, mqModel.getMQName(), mqModel.toMessage(), messagePostProcessor ->{
messagePostProcessor.getMessageProperties().setDelay(Math.toIntExact(delay * 1000));
return messagePostProcessor;
});
}else{
// fanout模式 的 routeKEY 没意义。 没有延迟属性
this.rabbitTemplate.convertAndSend(RabbitMQConfig.FANOUT_EXCHANGE_NAME_PREFIX + mqModel.getMQName(), null, mqModel.toMessage());
}
}
}
PayOrderMchNotifyMQ的 build方法:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PayOrderMchNotifyMQ extends AbstractMQ {
/** 【!重要配置项!】 定义MQ名称 **/
public static final String MQ_NAME = "QUEUE_PAY_ORDER_MCH_NOTIFY";
/** 内置msg 消息体定义 **/
private MsgPayload payload;
/** 【!重要配置项!】 定义Msg消息载体 **/
@Data
@AllArgsConstructor
public static class MsgPayload {
/** 通知单号 **/
private Long notifyId;
}
@Override
public String getMQName() {
return MQ_NAME;
}
/** 【!重要配置项!】 **/
@Override
public MQSendTypeEnum getMQType(){
return MQSendTypeEnum.QUEUE; // QUEUE - 点对点 、 BROADCAST - 广播模式
}
@Override
public String toMessage() {
return JSONObject.toJSONString(payload);
}
/** 【!重要配置项!】 构造MQModel , 一般用于发送MQ时 **/
public static PayOrderMchNotifyMQ build(Long notifyId){
return new PayOrderMchNotifyMQ(new MsgPayload(notifyId));
}
/** 解析MQ消息, 一般用于接收MQ消息时 **/
public static MsgPayload parse(String msg){
return JSON.parseObject(msg, MsgPayload.class);
}
/** 定义 IMQReceiver 接口: 项目实现该接口则可接收到对应的业务消息 **/
public interface IMQReceiver{
void receive(MsgPayload payload);
}
}
RabbitMQ的配置项
/**
* RabbitMQ的配置项
* 1. 注册全部定义好的Queue Bean
* 2. 动态注册fanout交换机
* 3. 将Queue模式绑定到延时消息的交换机
*
* @author terrfly
* @site https://www.jeequan.com
* @date 2021/7/23 16:33
*/
@Component
@ConditionalOnProperty(name = MQVenderCS.YML_VENDER_KEY, havingValue = MQVenderCS.RABBIT_MQ)
public class RabbitMQConfig {
/** 全局定义延迟交换机名称 **/
public static final String DELAYED_EXCHANGE_NAME = "delayedExchange";
/** 扇形交换机前缀(activeMQ中的topic模式), 需根据queue动态拼接 **/
public static final String FANOUT_EXCHANGE_NAME_PREFIX = "fanout_exchange_";
/** 注入延迟交换机Bean **/
@Autowired
@Qualifier(DELAYED_EXCHANGE_NAME)
private CustomExchange delayedExchange;
/** 注入rabbitMQBeanProcessor **/
@Autowired
private RabbitMQBeanProcessor rabbitMQBeanProcessor;
/** 在全部bean注册完成后再执行 **/
@PostConstruct
public void init(){
// 获取到所有的MQ定义
Set<Class<?>> set = ClassUtil.scanPackageBySuper(ClassUtil.getPackage(AbstractMQ.class), AbstractMQ.class);
for (Class<?> aClass : set) {
// 实例化
AbstractMQ amq = (AbstractMQ) ReflectUtil.newInstance(aClass);
// 注册Queue === new Queue(name), queue名称/bean名称 = mqName
rabbitMQBeanProcessor.beanDefinitionRegistry.registerBeanDefinition(amq.getMQName(),
BeanDefinitionBuilder.rootBeanDefinition(Queue.class).addConstructorArgValue(amq.getMQName()).getBeanDefinition());
// 广播模式
if(amq.getMQType() == MQSendTypeEnum.BROADCAST){
// 动态注册交换机, 交换机名称/bean名称 = FANOUT_EXCHANGE_NAME_PREFIX + amq.getMQName()
rabbitMQBeanProcessor.beanDefinitionRegistry.registerBeanDefinition(FANOUT_EXCHANGE_NAME_PREFIX +amq.getMQName(),
BeanDefinitionBuilder.genericBeanDefinition(FanoutExchange.class, () ->{
// 普通FanoutExchange 交换机
return new FanoutExchange(FANOUT_EXCHANGE_NAME_PREFIX +amq.getMQName(),true,false);
// 支持 延迟的 FanoutExchange 交换机, 配置无效果。
// Map<String, Object> args = new HashMap<>();
// args.put("x-delayed-type", ExchangeTypes.FANOUT);
// return new CustomExchange(RabbitMQConfig.DELAYED_EXCHANGE_NAME, "x-delayed-message", true, false, args);
}
).getBeanDefinition()
);
}else{
// 延迟交换机与Queue进行绑定, 绑定Bean名称 = mqName_DelayedBind
rabbitMQBeanProcessor.beanDefinitionRegistry.registerBeanDefinition(amq.getMQName() + "_DelayedBind",
BeanDefinitionBuilder.genericBeanDefinition(Binding.class, () ->
BindingBuilder.bind(SpringBeansUtil.getBean(amq.getMQName(), Queue.class)).to(delayedExchange).with(amq.getMQName()).noargs()
).getBeanDefinition()
);
}
}
}
}
RabbitMQBeanProcessor
/**
* 将spring容器的 [bean注册器]放置到属性中,为 RabbitConfig提供访问。
* 顺序:
* 1. postProcessBeanDefinitionRegistry (存放注册器)
* 2. postProcessBeanFactory (没有使用)
* 3. 注册延迟消息交换机的bean: delayedExchange
* 4. 动态配置RabbitMQ所需的bean。
*/
@Configuration
@ConditionalOnProperty(name = MQVenderCS.YML_VENDER_KEY, havingValue = MQVenderCS.RABBIT_MQ)
public class RabbitMQBeanProcessor implements BeanDefinitionRegistryPostProcessor {
//对标准BeanFactoryPostProcessor SPI 的扩展,允许在常规 BeanFactoryPostProcessor 检测开始之前注册进一步的 bean 定义。
// 特别是,BeanDefinitionRegistryPostProcessor 可以注册进一步的 bean 定义,
// 这些定义反过来定义 BeanFactoryPostProcessor 实例
/** bean注册器 **/
protected BeanDefinitionRegistry beanDefinitionRegistry;
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
this.beanDefinitionRegistry = beanDefinitionRegistry;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
/** 自定义交换机: 用于延迟消息 **/
@Bean(name = RabbitMQConfig.DELAYED_EXCHANGE_NAME)
CustomExchange delayedExchange() {
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "direct");
return new CustomExchange(RabbitMQConfig.DELAYED_EXCHANGE_NAME, "x-delayed-message", true, false, args);
}
}
MQ 线程池配置
@Configuration
@EnableAsync
public class MqThreadExecutor {
public static final String EXECUTOR_PAYORDER_MCH_NOTIFY = "mqQueue4PayOrderMchNotifyExecutor";
/*
* 功能描述:
* 支付结果通知到商户的异步执行器 (由于量大, 单独新建一个线程池处理, 之前的不做变动 )
* 20, 300, 10, 60 该配置: 同一时间最大并发量300,(已经验证通过, 商户都可以收到请求消息)
* 缓存队列尽量减少,否则将堵塞在队列中无法执行。 corePoolSize 根据机器的配置进行添加。此处设置的为20
*/
@Bean
public Executor mqQueue4PayOrderMchNotifyExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20); // 线程池维护线程的最少数量
executor.setMaxPoolSize(300); // 线程池维护线程的最大数量
executor.setQueueCapacity(10); // 缓存队列
executor.setThreadNamePrefix("payOrderMchNotifyExecutor-");
// rejection-policy:当pool已经达到max size的时候,如何处理新任务
// CALLER_RUNS:不在新线程中执行任务,而是由调用者所在的线程来执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); //对拒绝task的处理策略
executor.setKeepAliveSeconds(60); // 允许的空闲时间
executor.initialize();
return executor;
}
}
mq清除商户登录信息通知
/**
*
* 定义MQ消息格式
* 业务场景: [ 清除商户登录信息 ]
/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CleanMchLoginAuthCacheMQ extends AbstractMQ {
/** 【!重要配置项!】 定义MQ名称 **/
public static final String MQ_NAME = "QUEUE_CLEAN_MCH_LOGIN_AUTH_CACHE";
/** 内置msg 消息体定义 **/
private MsgPayload payload;
/** 【!重要配置项!】 定义Msg消息载体 **/
@Data
@AllArgsConstructor
public static class MsgPayload {
/** 用户ID集合 **/
private List<Long> userIdList;
}
@Override
public String getMQName() {
return MQ_NAME;
}
/** 【!重要配置项!】 **/
@Override
public MQSendTypeEnum getMQType(){
return MQSendTypeEnum.QUEUE; // QUEUE - 点对点 、 BROADCAST - 广播模式
}
@Override
public String toMessage() {
return JSONObject.toJSONString(payload);
}
/** 【!重要配置项!】 构造MQModel , 一般用于发送MQ时 **/
public static CleanMchLoginAuthCacheMQ build(List<Long> userIdList){
return new CleanMchLoginAuthCacheMQ(new MsgPayload(userIdList));
}
/** 解析MQ消息, 一般用于接收MQ消息时 **/
public static MsgPayload parse(String msg){
return JSON.parseObject(msg, MsgPayload.class);
}
/** 定义 IMQReceiver 接口: 项目实现该接口则可接收到对应的业务消息 **/
public interface IMQReceiver{
void receive(MsgPayload payload);
}
}
对应消息接收器:
/**
* rabbitMQ消息接收器:仅在vender=rabbitMQ时 && 项目实现IMQReceiver接口时 进行实例化
* 业务: 清除商户登录信息
* /
@Component
@ConditionalOnProperty(name = MQVenderCS.YML_VENDER_KEY, havingValue = MQVenderCS.RABBIT_MQ)
@ConditionalOnBean(CleanMchLoginAuthCacheMQ.IMQReceiver.class)
public class CleanMchLoginAuthCacheRabbitMQReceiver implements IMQMsgReceiver {
@Autowired
private CleanMchLoginAuthCacheMQ.IMQReceiver mqReceiver;
/** 接收 【 queue 】 类型的消息 **/
@Override
@Async(MqThreadExecutor.EXECUTOR_PAYORDER_MCH_NOTIFY)
@RabbitListener(queues = CleanMchLoginAuthCacheMQ.MQ_NAME)
public void receiveMsg(String msg){
mqReceiver.receive(CleanMchLoginAuthCacheMQ.parse(msg));
}
}
/**
* 接收MQ消息
* 业务: 清除商户登录信息
* @author terrfly
* @site https://www.jeequan.com
* @date 2021/7/27 9:23
*/
@Slf4j
@Component
public class CleanMchLoginAuthCacheMQReceiver implements CleanMchLoginAuthCacheMQ.IMQReceiver {
@Override
public void receive(CleanMchLoginAuthCacheMQ.MsgPayload payload) {
log.info("成功接收删除商户用户登录的订阅通知, msg={}", payload);
// 字符串转List<Long>
List<Long> userIdList = payload.getUserIdList();
// 删除redis用户缓存
if(userIdList == null || userIdList.isEmpty()){
log.info("用户ID为空");
return ;
}
for (Long sysUserId : userIdList) {
Collection<String> cacheKeyList = RedisUtil.keys(CS.getCacheKeyToken(sysUserId, "*"));
if(cacheKeyList == null || cacheKeyList.isEmpty()){
continue;
}
for (String cacheKey : cacheKeyList) {
// 删除用户Redis信息
RedisUtil.del(cacheKey);
continue;
}
}
log.info("无权限登录用户信息已清除");
}
}
mq支付订单商户通知
@Component
@ConditionalOnProperty(name = MQVenderCS.YML_VENDER_KEY, havingValue = MQVenderCS.RABBIT_MQ)
@ConditionalOnBean(PayOrderMchNotifyMQ.IMQReceiver.class)
public class PayOrderMchNotifyRabbitMQReceiver implements IMQMsgReceiver {
@Autowired
private PayOrderMchNotifyMQ.IMQReceiver mqReceiver;
/** 接收 【 queue 】 类型的消息 **/
@Override
@Async(MqThreadExecutor.EXECUTOR_PAYORDER_MCH_NOTIFY)
// @Async中的参数用于指定使用哪一个线程池执行
@RabbitListener(queues = PayOrderMchNotifyMQ.MQ_NAME)
public void receiveMsg(String msg){
mqReceiver.receive(PayOrderMchNotifyMQ.parse(msg));
}
}
PayOrderMchNotifyMQ:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PayOrderMchNotifyMQ extends AbstractMQ {
/** 【!重要配置项!】 定义MQ名称 **/
public static final String MQ_NAME = "QUEUE_PAY_ORDER_MCH_NOTIFY";
/** 内置msg 消息体定义 **/
private MsgPayload payload;
/** 【!重要配置项!】 定义Msg消息载体 **/
@Data
@AllArgsConstructor
public static class MsgPayload {
/** 通知单号 **/
private Long notifyId;
}
@Override
public String getMQName() {
return MQ_NAME;
}
/** 【!重要配置项!】 **/
@Override
public MQSendTypeEnum getMQType(){
return MQSendTypeEnum.QUEUE; // QUEUE - 点对点 、 BROADCAST - 广播模式
}
@Override
public String toMessage() {
return JSONObject.toJSONString(payload);
}
/** 【!重要配置项!】 构造MQModel , 一般用于发送MQ时 **/
public static PayOrderMchNotifyMQ build(Long notifyId){
return new PayOrderMchNotifyMQ(new MsgPayload(notifyId));
}
/** 解析MQ消息, 一般用于接收MQ消息时 **/
public static MsgPayload parse(String msg){
return JSON.parseObject(msg, MsgPayload.class);
}
/** 定义 IMQReceiver 接口: 项目实现该接口则可接收到对应的业务消息 **/
public interface IMQReceiver{
void receive(MsgPayload payload);
}
}