前几天将黑马的RabbitMQ看完,最后留了个作业,做了大概一个晚上,其中遇到了一些问题也给大伙分享分享。
先叠个甲,有些用词可能不是很专业,请大家见谅哈,记得先把延迟消息的插件给下好。
1.在trade模块中添加OrderStatusListener,注意!!文档中的代码是错误的,他交换机没加延迟属性,我找半天这错误!!!
package com.hmall.trade.listener;
import com.hmall.api.client.PayClient;
import com.hmall.api.dto.PayOrderDTO;
import com.hmall.common.domain.MultiDelayMessage;
import com.hmall.trade.constants.MqConstants;
import com.hmall.trade.domain.po.Order;
import com.hmall.trade.service.IOrderService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
@Slf4j
@Component
@RequiredArgsConstructor
public class OrderStatusListener {
private final IOrderService orderService;
private final PayClient payClient;
private final RabbitTemplate rabbitTemplate;
//自己发送自己监听是吧
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = MqConstants.DELAY_ORDER_QUEUE, durable = "true"),
exchange = @Exchange(name = MqConstants.DELAY_EXCHANGE, delayed = "true",type = ExchangeTypes.TOPIC),
key = MqConstants.DELAY_ORDER_ROUTING_KEY
))
public void listenOrderCheckDelayMessage(MultiDelayMessage<Long> msg) {
log.info("接收到消息");
// 1.获取消息中的订单id
Long orderId = msg.getData();
// 2.查询订单,判断状态:1是未支付,大于1则是已支付或已关闭
Order order = orderService.getById(orderId);
if (order == null || order.getStatus() > 1) {
log.info("订单不存在或交易已经结束,放弃处理");
// 订单不存在或交易已经结束,放弃处理
return;
}
// 3.可能是未支付,查询支付服务
PayOrderDTO payOrder = payClient.queryPayOrderByBizOrderNo(orderId);
if (payOrder != null && payOrder.getStatus() == 3) {
// 支付成功,更新订单状态
orderService.markOrderPaySuccess(orderId);
log.info("支付成功,更新订单状态");
return;
}
// 4.确定未支付,判断是否还有剩余延迟时间
if (msg.hasNextDelay()) {
log.info("重发延迟消息");
// 4.1.有延迟时间,需要重发延迟消息,先获取延迟时间的int值
int delayVal = msg.removeNextDelay().intValue();
log.info("延迟时间是:{}",delayVal);
// 4.2.发送延迟消息
rabbitTemplate.convertAndSend(MqConstants.DELAY_EXCHANGE, MqConstants.DELAY_ORDER_ROUTING_KEY, msg,
message -> {
message.getMessageProperties().setDelay(delayVal);
return message;
});
return;
}
// 5.没有剩余延迟时间了,说明订单超时未支付,需要取消订单,并且返回我们的库存
log.info("准备取消!");
orderService.cancelOrder(orderId);
}
}
这里可以直接用lamda表达式而不是new MessagePostProcessor,这好像是什么新特性吧?更简洁了一些 。
2. 为了追求代码简洁我们在common服务中将MessagePostProcessor封装成自己的一个类
package com.hmall.common.mq;
import lombok.RequiredArgsConstructor;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
@RequiredArgsConstructor
public class DelayMessageProcessor implements MessagePostProcessor {
private final int delay;
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setDelay(delay);
return message;
}
}
好了基本的配置基本完成了,我们可以开始做延迟消息的发送了
3.首先找到trade服务中的OrderServicelmpl
因为我们的思路是在生成订单后开始发送延迟消息,进行对用户 是否付款这个行为 的检测,所以我们对creatOrder进行改造:
@Override
@Transactional
public Long createOrder(OrderFormDTO orderFormDTO) {
// 1.订单数据
Order order = new Order();
// 1.1.查询商品
List<OrderDetailDTO> detailDTOS = orderFormDTO.getDetails();
// 1.2.获取商品id和数量的Map
Map<Long, Integer> itemNumMap = detailDTOS.stream()
.collect(Collectors.toMap(OrderDetailDTO::getItemId, OrderDetailDTO::getNum));
Set<Long> itemIds = itemNumMap.keySet();
// 1.3.查询商品
List<ItemDTO> items = itemClient.getItemList(itemIds);
if (items == null || items.size() < itemIds.size()) {
throw new BadRequestException("商品不存在");
}
// 1.4.基于商品价格、购买数量计算商品总价:totalFee
int total = 0;
for (ItemDTO item : items) {
total += item.getPrice() * itemNumMap.get(item.getId());
}
order.setTotalFee(total);
// 1.5.其它属性
order.setPaymentType(orderFormDTO.getPaymentType());
order.setUserId(UserContext.getUser());
order.setStatus(1);
// 1.6.将Order写入数据库order表中
save(order);
// 2.保存订单详情
List<OrderDetail> details = buildDetails(order.getId(), items, itemNumMap);
detailService.saveBatch(details);
// 3.清理购物车商品
cartClient.deleteCartItemByIds(itemIds);
// 4.扣减库存
try {
itemClient.deductStock(detailDTOS);
} catch (Exception e) {
throw new RuntimeException("库存不足!");
}
//5.TODO 延迟检测 重中之重
// 创建我们定义好的对象
try{
log.info("发送消息了");
MultiDelayMessage<Long> msg=MultiDelayMessage.of(order.getId(),10000L,10000L);
log.info("当前mesg:{}",msg);
rabbitTemplate.convertAndSend(MqConstants.DELAY_EXCHANGE,MqConstants.DELAY_ORDER_ROUTING_KEY,msg,
new DelayMessageProcessor(msg.removeNextDelay().intValue()));
}
catch(Exception e){
log.info("延迟消息发送异常:{}",e);
}
return order.getId();
}
在其中我没有定义30分钟,这边只是测试,一共就定义两次10秒,也方便我们调试。
那么在哪里接受呢,就是上面我们定义的listener了,相关可以看看视频,然后最后我们在说一下老师让我们自己补充的cancelOrder方法
4.cancelOrder方法
我们的思路就是超时了将详情订单和支付订单的状态都设置为取消即可
/**
* 取消订单并且让库存加回来
* @param orderId
*/
@Override
public void cancelOrder(Long orderId) {
log.info("恢复订单拿到当前orderid:{}",orderId);
//1. 先将生成订单给status设置为取消捏
lambdaUpdate()
.set(Order::getStatus,5)
.eq(Order::getId,orderId)
.update();
//2. 将支付订单设为取消
payClient.queryPayOrderByBizOrderNo(orderId);
//3. 将响应商品的库存加回来
List<OrderDetail> list = detailService.lambdaQuery().eq(OrderDetail::getOrderId,orderId).list();
itemClient.restoreItems(list);
}
然后因为这里要恢复物品的库存,设计到不同的微服务,所以要用到openFeign
ItemClient:
package com.hmall.api.client;
import com.hmall.api.client.fallback.ItemClientFallbackFactory;
import com.hmall.api.dto.ItemDTO;
import com.hmall.api.dto.OrderDetailDTO;
import com.hmall.common.domain.OrderDetail;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Collection;
import java.util.List;
@FeignClient(value = "item-service",fallbackFactory = ItemClientFallbackFactory.class)
public interface ItemClient {
@GetMapping("/items")
List<ItemDTO> getItemList(@RequestParam("ids") Collection<Long> ids);
@PutMapping("/items/stock/deduct")
public void deductStock(@RequestBody List<OrderDetailDTO> items);
@PutMapping("/items/restore")
public void restoreItems(@RequestBody List<OrderDetail> orderDetails);
}
在ItemController添加
@ApiOperation("恢复商品库存")
@PutMapping("/restore")
public void restoreItems(@RequestBody List<OrderDetail> orderDetails){
itemService.restore(orderDetails);
}
ItemServiceImpl添加:
/**
* 取消订单,恢复商品数据
* @param orderDetails
*/
@Override
public void restore(List<OrderDetail> orderDetails) {
//感觉还可以优化,但是我懒
log.info("恢复商品数据咯:{}",orderDetails);
for (OrderDetail detail:orderDetails
) {
//先拿到当前item对象
Item one = lambdaQuery()
.eq(Item::getId,detail.getItemId())
.one();
log.info("当前item的库存");
lambdaUpdate()
.eq(Item::getId,detail.getItemId())
.set(Item::getStock,one.getStock()+detail.getNum())
.update();
}
}
最后运行即可
不懂的可以评论区问,私信不一定及时看