网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
如果消费者未收到消息,可能有以下几种原因:
- 消息丢失:在消息生产者发送消息到 RabbitMQ 服务器后,由于网络问题或者其他原因导致消息丢失,这种情况下消费者是不会收到消息的。解决这个问题需要确保消息生产者发送消息时能够成功连接到 RabbitMQ 服务器,并且消息能够正确发送。
- 消费者无法处理消息:如果消费者无法正确处理消息,可能是因为消费者代码出现了错误或者处理消息的逻辑不完善,这种情况下 RabbitMQ 不会触发回调函数。解决这个问题需要检查消费者代码逻辑是否正确,并确保能够处理各种异常情况。
- 队列配置问题:如果队列配置不正确,比如消费者没有正确绑定到队列,或者队列设置了错误的参数,可能会导致消费者无法接收到消息。解决这个问题需要检查队列的配置,并确保消费者能够正确订阅到消息。
2、接口定义
生产者业务层:
/**
* 请求支付宝查询支付结果
*
* @param payNo 支付记录id
* @return 支付记录信息
*/
@Override
public PayRecordDto queryPayResult(String payNo) {
//获得初始化的AlipayClient
AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.URL,
APP_ID,APP_PRIVATE_KEY,
"json",
AlipayConfig.CHARSET,
ALIPAY_PUBLIC_KEY,
AlipayConfig.SIGNTYPE);
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", payNo);
request.setBizContent(bizContent.toString());
AlipayTradeQueryResponse response = null;
try {
response = alipayClient.execute(request);
} catch (AlipayApiException e) {
log.error("{}:查询支付宝支付结果错误!",payNo);
return null;
}
if (!response.isSuccess()) {
log.error("{}:查询支付宝支付结果失败!",payNo);
return null;
}
String resultJson = response.getBody();
//转map
Map resultMap = JSON.parseObject(resultJson, Map.class);
Map alipay_trade_query_response = (Map) resultMap.get("alipay_trade_query_response");
//交易状态
String tradeStatus = (String) alipay_trade_query_response.get("trade_status");
//支付宝交易号
String tradeNo = (String) alipay_trade_query_response.get("trade_no");
PayStatusDto payStatusDto = new PayStatusDto();
payStatusDto.setTrade_status(tradeStatus);
payStatusDto.setTrade_no(tradeNo);
payStatusDto.setOut_trade_no(payNo);
payStatusDto.setApp_id(APP_ID);
//处理订单状态
return this.handlePayStatus(payStatusDto);
}
/**
* 处理订单状态,更新xc_pay_record表
* @return
*/
public PayRecordDto handlePayStatus(PayStatusDto dto) {
PayRecordDto payRecordDto = new PayRecordDto();
String payNo = dto.getOut_trade_no();
String tradeNo = dto.getTrade_no();
String tradeStatus = dto.getTrade_status();
XcPayRecord xcPayRecord = xcPayRecordMapper.selectOne(new LambdaQueryWrapper<XcPayRecord>().eq(XcPayRecord::getPayNo, payNo));
if (null == xcPayRecord ){
log.error("{}:查询订单记录不存在!",tradeNo);
XueChengPlusException.cast("查询订单记录不存在!");
}
if (xcPayRecord.getStatus().equals("601002")){
BeanUtils.copyProperties(xcPayRecord,payRecordDto);
return payRecordDto;
}
//修改xc_pay_record和xc_orders 交易状态
switch (tradeStatus) {
case "TRADE_CLOSED":
xcPayRecord.setStatus("601003");
break;
case "TRADE_SUCCESS":
case "TRADE_FINISHED":
xcPayRecord.setStatus("601002");
xcPayRecord.setPaySuccessTime(LocalDateTime.now());
break;
}
xcPayRecord.setOutPayNo(tradeNo);
xcPayRecord.setOutPayChannel("alipay");
xcPayRecordMapper.updateById(xcPayRecord);
BeanUtils.copyProperties(xcPayRecord,payRecordDto);
//通过orderId查询订单表信息
Long orderId = xcPayRecord.getOrderId();
XcOrders xcOrders = ordersMapper.selectOne(new LambdaQueryWrapper<XcOrders>().eq(XcOrders::getId, orderId));
if (null == xcOrders ){
log.error("{}:查询订单不存在!",tradeNo);
XueChengPlusException.cast("查询订单不存在!");
}
//存入mq_message表
MqMessage message = mqMessageService.addMessage("payresult_notify", xcOrders.getOutBusinessId(), xcOrders.getOrderType(), null);
//将消息发送至队列,通知learning服务
this.notifyPayResult(message);
return payRecordDto;
}
/**
* 发送通知结果
*
* @param message
*/
@Override
public void notifyPayResult(MqMessage message) {
//1、消息体,转json
String msg = JSON.toJSONString(message);
//设置消息持久化
Message msgObj = MessageBuilder.withBody(msg.getBytes(StandardCharsets.UTF_8))
.setDeliveryMode(MessageDeliveryMode.PERSISTENT)
.build();
// 2.全局唯一的消息ID,需要封装到CorrelationData中
CorrelationData correlationData = new CorrelationData(message.getId().toString());
// 3.添加callback 回调函数,接收方接受成功或失败后回调
correlationData.getFuture().addCallback(
result -> {
if(result.isAck()){
// 3.1.ack,消息成功
log.debug("通知支付结果消息发送成功, ID:{}", correlationData.getId());
//删除消息表中的记录
mqMessageService.completed(message.getId());
}else{
// 3.2.nack,消息失败
log.error("通知支付结果消息发送失败, ID:{}, 原因{}",correlationData.getId(), result.getReason());
}
},
ex -> log.error("消息发送异常, ID:{}, 原因{}",correlationData.getId(),ex.getMessage())
);
// 发送消息 因为是广播模式所以路由键为空
rabbitTemplate.convertAndSend(PayNotifyConfig.PAYNOTIFY_EXCHANGE_FANOUT, "", msgObj,correlationData);
}
支付宝异步回调接口同理也调用了handlePayStatus方法
消费者业务层:
@Service
@Slf4j
@Transactional
public class ReceviceOrderMessageServiceImpl implements ReceviceOrderMessageService {
/*
选课记录表
*/
@Autowired
private XcChooseCourseMapper chooseCourseMapper;
/*
课程表
*/
@Autowired
private XcCourseTablesMapper courseTablesMapper;
@Autowired
private MyCourseTables myCourseTables;
@Autowired
private ReceviceOrderMessageService receviceOrderMessageService;
@Resource
private RedisTemplate<String,Integer> redisTemplate;
@Resource
private RedissonClient redissonClient;
private static final String QUEUE_KEY = "learning:";
private static final String REDISSON_KEY = "redisson:";
@Override
@RabbitListener(queues = PayNotifyConfig.PAYNOTIFY_QUEUE)
public void receiveMessage(Message message, Channel channel) {
byte[] body = message.getBody();
long deliverTag = message.getMessageProperties().getDeliveryTag();
String jsonStr = new String(body);
MqMessage mqMessage = JSON.parseObject(jsonStr, MqMessage.class);
//得到选课id
String businessKey1 = mqMessage.getBusinessKey1();
//得到课程类型
String businessKey2 = mqMessage.getBusinessKey2();
//处理课程信息
Integer count = 0;
try {
count = redisTemplate.opsForValue().get(QUEUE_KEY + businessKey1);
if (!ObjectUtils.isEmpty(count) && count == 3){
log.error("消息已达到最大重试次数:{},作丢弃处理",count);
channel.basicNack(deliverTag,false,false);
return;
}
this.receviceOrderMessageService.handleCourseDBData(businessKey1,businessKey2);
int i = 1/0;
channel.basicAck(deliverTag,true);
log.info("消息手动确认成功!");
} catch (Exception e) {
try {
channel.basicNack(deliverTag,false,true);
RLock lock = redissonClient.getLock(REDISSON_KEY + businessKey1);
if (lock.tryLock()){
try {
redisTemplate.opsForValue().set(QUEUE_KEY + businessKey1, count == null ? 1 : count + 1,60, TimeUnit.SECONDS);
} finally {
lock.unlock();
}
}
} catch (IOException ex) {
log.error("重新放入队列失败,失败原因:{}",e.getMessage(),e);
}
log.error("消费者出错,mq参数:{},错误信息:{}",message,e.getMessage(),e);
}
}
/**
* 处理课程表,选课记录表
* @param businessKey1 选课表id
* @param businessKey2 课程类型
* @return
*/
public boolean handleCourseDBData(String businessKey1, String businessKey2) {
//根据课程id查询选课记录表
XcChooseCourse xcChooseCourse = chooseCourseMapper.selectOne(new LambdaQueryWrapper<XcChooseCourse>().
eq(XcChooseCourse::getId, businessKey1));
if (ObjectUtils.isEmpty(xcChooseCourse)){
log.error("根据课程ID:{}查询到的选课记录为空!",businessKey1);
return false;
}
if (!businessKey2.equals("60201")){
return false;
}
//修改状态为701001选课成功
xcChooseCourse.setStatus("701001");
int i = chooseCourseMapper.updateById(xcChooseCourse);
if (i<1){
log.error("更新选课记录表失败!选课ID:{}",businessKey1);
XueChengPlusException.cast("更新选课记录表失败!");
}
//向课程表插入记录
this.myCourseTables.initCourseTableData(xcChooseCourse);
return true;
}
}
此处消费者使用手动确认消息+异常重试机制。异常重试超过3次就丢弃消息,也可以使用手动确认消息+死信队列模式。
Q2:在配置文件开启了消费者手动确认,如果消费者拒绝签收,那么生产者的ConfirmCallback和ReturnCallBack回调方法会触发吗,这条消息会怎么样?
如果消费者拒绝签收消息(通过调用basicNack或basicReject方法),生产者的confirmCallback和returnCallback回调方法不会触发。因为消息已经被消费者处理完毕,不会再返回给生产者。
当消费者拒绝签收消息时,这条消息的处理方式取决于拒绝签收的方式和设置。具体情况如下:
- basicNack方法:如果消费者使用
basicNack
方法拒绝签收消息,并且将requeue
参数设置为true
,则消息会重新放回队列,等待重新消费。如果将requeue
参数设置为false
,则消息会被丢弃。
2.basicReject方法:如果消费者使用basicReject
方法拒绝签收消息,并且将requeue
参数设置为true
,则消息会重新放回队列,等待重新消费。如果将requeue
参数设置为false
,则消息会被丢弃。
Q3:我使用basicNack,把requeue设成了true,并且在配置文件中设置了重试次数为3次,但是因为程序中存在异常,无论重试多少次都不会成功,会怎么样?
消息会被放回队列。某种程度上手动ack和配置重试次数是互斥的。为了避免无限重试,可以设置重试n次后丢弃消息,或是放入死信队列的方式。
3、测试
生产者下单成功发送消息
消息发送到交换机,触发回调函数
消费者接收消息并且手动确认成功
消费者方手动制造一个异常
消息成功发送至队列,回调函数触发
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
bad24d78a648ac4cdeb0e2ef.png)
[外链图片转存中…(img-Qq1jD4fV-1715776601234)]
[外链图片转存中…(img-lOP9w2J5-1715776601235)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!