Bootstrap

java 实现按照时间梯度异步通知第三方接口

业务背景

       在对外提供的API 接口中,处理完自身的业务逻辑后需要调用第三方系统接口,将相应的处理结果通知给对方,就像微信、支付宝支付后 异步通知支付结果一样,按照1s,2s,5s,1m,5m.....这种自定义的的时间梯度来通知第三方接口。

实现思路

       在业务完成后把要推送的消息存入数据库,并且发送至mq的延时消息队列,在mq 消费时判断本次推送等级并且计算下一等级推送时间,如果本次回调第三方未得到正确响应则继续发送下一等级的mq 延时队列。

使用技术

       springboot 2.x + rocketmq-spring-boot-starter-2.0.4 + mysql

 

数据库表设计

   回调通知数据库表设计:

CREATE TABLE `tb_callback_notify` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_no` varchar(50) DEFAULT NULL COMMENT '用户编号',
  `order_no` varchar(50) DEFAULT NULL COMMENT '订单号',
  `notify_url` varchar(255) DEFAULT NULL COMMENT '通知url',
  `notify_data` text COMMENT '通知内容',
  `notify_times` int(11) DEFAULT '0' COMMENT '通知次数(等级)',
  `last_notify_time` datetime DEFAULT NULL COMMENT '最后一次通知时间',
  `next_notify_time` datetime DEFAULT NULL COMMENT '下次通知时间',
  `status` tinyint(1) DEFAULT '1' COMMENT '状态 1 未完成 2已完成',
  `create_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

 

自定义时间梯度:

在rocketmq-spring-boot-starter中,实现延时队列有固定的18个等级,每个等级对应的延时时长分别为:1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h,在自己的业务中 我们可以选取部分等级来作为回调通知的时间梯度,我这里选取了1s 5s 10s 30s 1m 5m 10m 30m 1h 2h 作为我方通知的时间梯度策略

 

代码实现

public class Constant {
    /**
     * 最大回调通知次数
     */
    public static final Integer MAX_NOTIFY_TIMES = 10;
    
    /**
     * rocekt mq 延时等级对应的秒数
     */
    public static final Integer[] ROCKET_MQ_DELAY_LEVEL_SECOND = {1,5,10,30,60,120,180,240,300,360,420,480,540,600,1200,1800,3600,7200};
    
    /**
     * 回调通知topic
     */
    public static final String QUEUE_CALLBACK_TOPIC="queue_callback";
    
    /**
     * 回调通知group
     */
    public static final String QUEUE_GROUP_CALLBACK_TOPIC="queue_group_callback";
    
    /**
     * 系统通知为最大10次,每次通知对应mq 等级<br/>
     * 回调通知频率,对应rocketmq 延时等级策略<br/>
     * rocketmq 延时等级: 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h<br/>
     * 取mq的 1s 5s 10s 30s 1m 5m 10m 30m 1h 2h 作为我方通知策略
     */
    public static final Integer[] CALLBACK_PUSH_FREQUENCY_TO_MQ_LEVEL = {1,2,3,4,5,9,14,16,17,18};
}

业务处理完后异步调用此service的callback方法进行通知

@Service
public class CallbackNotifyServiceImpl extends ServiceImpl<CallbackNotifyMapper, CallbackNotify> implements ICallbackNotifyService {

	@Autowired
	private CallbackNotifyMapper callbackNotifyMapper;
	
	@Autowired
	private RocketMQTemplate rocketMQTemplate;
	
	@Override
	public void callback(String userNo, String orderNo, String notifyUrl, String notifyData) {
		// 校验是否正在进行回调
		CallbackNotify notify = callbackNotifyMapper.selectOne(new QueryWrapper<CallbackNotify>().eq("order_no", orderNo));
		if(Assert.isNullOrEmpty(notify)) {
			Date now = new Date();
			notify = new CallbackNotify();
			notify.setOrderNo(orderNo);
			notify.setUserNo(userNo);
			notify.setStatus(1);
			notify.setCreateTime(now);
			notify.setNotifyUrl(notifyUrl);
			notify.setNotifyData(notifyData);
			notify.setNotifyTimes(0);
			callbackNotifyMapper.insert(notify);
		}
		rocketMQTemplate.syncSend(Constant.QUEUE_CALLBACK_TOPIC, MessageBuilder.withPayload(JSONObject.toJSONString(notify)).build());
	}

}

rocketMQ监听器

/**
 * 回调消息推送监听<br/>
 * 如果此次推送失败或者收到的响应是false的时候 重新计算下次推送时间,并且再次发送下一级延时等级的队列
 * @author francis
 *
 */
@Slf4j
@Component
@RocketMQMessageListener(topic = Constant.QUEUE_CALLBACK_TOPIC, consumerGroup = Constant.QUEUE_GROUP_CALLBACK_TOPIC)
public class CallbackNotifyListener implements RocketMQListener<MessageExt>  {

	@Autowired
	private ICallbackNotifyService callbackNotifyService;
	
	@Value("${rocketmq.producer.send-message-timeout}")
	private Integer messageTimeOut;
	
	@Autowired
	private RocketMQTemplate rocketMQTemplate;
	
	private final RestTemplate restTemplate = new RestTemplateBuilder().setConnectTimeout(Duration.ofMillis(3000)).setReadTimeout(Duration.ofMillis(4000)).build();
	
	@Override
	public void onMessage(MessageExt message) {
		byte[] body = message.getBody();
		String msg = new String(body);
		log.info("接收到消息:{}", msg);
		Date now = new Date();
		CallbackNotify callbackNotify = JSONObject.parseObject(msg, CallbackNotify.class);
		callbackNotify.setLastNotifyTime(now);
		Integer times = callbackNotify.getNotifyTimes();
		times++;
		// 大于最大通知次数 直接结束
		if(times > Constant.MAX_NOTIFY_TIMES) {
			return;
		}
		// 下一次推送延时等级
		Integer nextLevel = Constant.CALLBACK_PUSH_FREQUENCY_TO_MQ_LEVEL[times];
		// 计算下一次推送的秒
		Integer nextNotifySecond = Constant.ROCKET_MQ_DELAY_LEVEL_SECOND[nextLevel];
		callbackNotify.setNextNotifyTime(DateUtil.offsetSecond(now, nextNotifySecond));
		callbackNotify.setNotifyTimes(times);
		
		JSONObject notifyData = JSONObject.parseObject(callbackNotify.getNotifyData());
		HttpHeaders headers = new HttpHeaders();
		headers.setContentType(MediaType.APPLICATION_JSON);
		HttpEntity<JSONObject> request = new HttpEntity<JSONObject>(notifyData, headers);
		try {
		    ResponseEntity<String> response = restTemplate.postForEntity(callbackNotify.getNotifyUrl(), request, String.class);
			// 推送成功
			if(Boolean.TRUE.toString().equals(response.getBody())) {
				callbackNotify.setStatus(2);
			}else {
				rocketMQTemplate.syncSend(Constant.QUEUE_CALLBACK_TOPIC, MessageBuilder.withPayload(JSONObject.toJSONString(callbackNotify)).build(), messageTimeOut, nextLevel);
			}
		} catch (Exception e) {
			log.error("推送回调消息异常", e);
			rocketMQTemplate.syncSend(Constant.QUEUE_CALLBACK_TOPIC, MessageBuilder.withPayload(JSONObject.toJSONString(callbackNotify)).build(), messageTimeOut, nextLevel);
		}
		callbackNotify.setLastNotifyTime(now);
		callbackNotifyService.updateById(callbackNotify);
	}

}

 

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;