mirror of
https://gitee.com/youlaitech/youlai-mall.git
synced 2024-12-23 05:00:25 +08:00
feat: Seata TCC模式改造
1、Seata TCC 订单功能添加 2、冻结库存TCC模式添加
This commit is contained in:
parent
349705027c
commit
f6e5e6bd07
@ -29,6 +29,11 @@ public interface IOrderService extends IService<OmsOrder> {
|
|||||||
*/
|
*/
|
||||||
OrderSubmitVO submit(OrderSubmitDTO orderSubmitDTO) ;
|
OrderSubmitVO submit(OrderSubmitDTO orderSubmitDTO) ;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单提交
|
||||||
|
*/
|
||||||
|
OrderSubmitVO submitTcc(OrderSubmitDTO orderSubmitDTO) ;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单支付
|
* 订单支付
|
||||||
*/
|
*/
|
||||||
|
@ -15,17 +15,18 @@ import com.youlai.mall.oms.enums.OrderStatusEnum;
|
|||||||
import com.youlai.mall.oms.enums.OrderTypeEnum;
|
import com.youlai.mall.oms.enums.OrderTypeEnum;
|
||||||
import com.youlai.mall.oms.enums.PayTypeEnum;
|
import com.youlai.mall.oms.enums.PayTypeEnum;
|
||||||
import com.youlai.mall.oms.mapper.OrderMapper;
|
import com.youlai.mall.oms.mapper.OrderMapper;
|
||||||
import com.youlai.mall.oms.pojo.entity.OmsOrder;
|
|
||||||
import com.youlai.mall.oms.pojo.entity.OmsOrderItem;
|
|
||||||
import com.youlai.mall.oms.pojo.dto.OrderConfirmDTO;
|
import com.youlai.mall.oms.pojo.dto.OrderConfirmDTO;
|
||||||
import com.youlai.mall.oms.pojo.dto.OrderItemDTO;
|
import com.youlai.mall.oms.pojo.dto.OrderItemDTO;
|
||||||
import com.youlai.mall.oms.pojo.dto.OrderSubmitDTO;
|
import com.youlai.mall.oms.pojo.dto.OrderSubmitDTO;
|
||||||
|
import com.youlai.mall.oms.pojo.entity.OmsOrder;
|
||||||
|
import com.youlai.mall.oms.pojo.entity.OmsOrderItem;
|
||||||
import com.youlai.mall.oms.pojo.vo.CartVO;
|
import com.youlai.mall.oms.pojo.vo.CartVO;
|
||||||
import com.youlai.mall.oms.pojo.vo.OrderConfirmVO;
|
import com.youlai.mall.oms.pojo.vo.OrderConfirmVO;
|
||||||
import com.youlai.mall.oms.pojo.vo.OrderSubmitVO;
|
import com.youlai.mall.oms.pojo.vo.OrderSubmitVO;
|
||||||
import com.youlai.mall.oms.service.ICartService;
|
import com.youlai.mall.oms.service.ICartService;
|
||||||
import com.youlai.mall.oms.service.IOrderItemService;
|
import com.youlai.mall.oms.service.IOrderItemService;
|
||||||
import com.youlai.mall.oms.service.IOrderService;
|
import com.youlai.mall.oms.service.IOrderService;
|
||||||
|
import com.youlai.mall.oms.tcc.service.SeataTccOrderService;
|
||||||
import com.youlai.mall.pms.api.SkuFeignClient;
|
import com.youlai.mall.pms.api.SkuFeignClient;
|
||||||
import com.youlai.mall.pms.pojo.dto.SkuDTO;
|
import com.youlai.mall.pms.pojo.dto.SkuDTO;
|
||||||
import com.youlai.mall.pms.pojo.dto.SkuLockDTO;
|
import com.youlai.mall.pms.pojo.dto.SkuLockDTO;
|
||||||
@ -56,16 +57,17 @@ import static com.youlai.mall.oms.constant.OmsConstants.*;
|
|||||||
@Service
|
@Service
|
||||||
public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> implements IOrderService {
|
public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> implements IOrderService {
|
||||||
|
|
||||||
private ICartService cartService;
|
private final ICartService cartService;
|
||||||
private SkuFeignClient skuFeignService;
|
private final SkuFeignClient skuFeignService;
|
||||||
private MemberAddressFeignClient addressFeignService;
|
private final MemberAddressFeignClient addressFeignService;
|
||||||
private IOrderItemService orderItemService;
|
private final IOrderItemService orderItemService;
|
||||||
private RabbitTemplate rabbitTemplate;
|
private final RabbitTemplate rabbitTemplate;
|
||||||
private StringRedisTemplate redisTemplate;
|
private final StringRedisTemplate redisTemplate;
|
||||||
private ThreadPoolExecutor threadPoolExecutor;
|
private final ThreadPoolExecutor threadPoolExecutor;
|
||||||
private MemberFeignClient memberFeignClient;
|
private final MemberFeignClient memberFeignClient;
|
||||||
|
|
||||||
private BusinessNoGenerator businessNoGenerator;
|
private final BusinessNoGenerator businessNoGenerator;
|
||||||
|
private final SeataTccOrderService seataTccOrderService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单确认
|
* 订单确认
|
||||||
@ -175,7 +177,6 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
|
|||||||
if (!Result.success().getCode().equals(lockResult.getCode())) {
|
if (!Result.success().getCode().equals(lockResult.getCode())) {
|
||||||
throw new BizException(Result.failed().getMsg());
|
throw new BizException(Result.failed().getMsg());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建订单(状态:待支付)
|
// 创建订单(状态:待支付)
|
||||||
OmsOrder order = new OmsOrder();
|
OmsOrder order = new OmsOrder();
|
||||||
order.setOrderSn(orderToken) // 把orderToken赋值给订单编号【!】
|
order.setOrderSn(orderToken) // 把orderToken赋值给订单编号【!】
|
||||||
@ -213,6 +214,63 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
|
|||||||
return submitVO;
|
return submitVO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@GlobalTransactional(rollbackFor = Exception.class)
|
||||||
|
public OrderSubmitVO submitTcc(OrderSubmitDTO submitDTO) {
|
||||||
|
log.info("=======================订单提交=======================\n订单提交信息:{}", submitDTO);
|
||||||
|
// 订单重复提交校验
|
||||||
|
String orderToken = submitDTO.getOrderToken();
|
||||||
|
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(RELEASE_LOCK_LUA_SCRIPT, Long.class);
|
||||||
|
Long result = this.redisTemplate.execute(redisScript, Collections.singletonList(ORDER_TOKEN_PREFIX + orderToken), orderToken);
|
||||||
|
|
||||||
|
if (!ObjectUtil.equals(result, RELEASE_LOCK_SUCCESS_RESULT)) {
|
||||||
|
throw new BizException("订单不可重复提交");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<OrderItemDTO> orderItems = submitDTO.getOrderItems();
|
||||||
|
if (CollectionUtil.isEmpty(orderItems)) {
|
||||||
|
throw new BizException("订单没有商品,请选择商品后提交");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订单验价
|
||||||
|
Long currentTotalPrice = orderItems.stream().map(item -> {
|
||||||
|
SkuDTO sku = skuFeignService.getSkuById(item.getSkuId()).getData();
|
||||||
|
if (sku != null) {
|
||||||
|
return sku.getPrice() * item.getCount();
|
||||||
|
}
|
||||||
|
return 0l;
|
||||||
|
}).reduce(0l, Long::sum);
|
||||||
|
|
||||||
|
if (currentTotalPrice.compareTo(submitDTO.getTotalPrice()) != 0) {
|
||||||
|
throw new BizException("页面已过期,请重新刷新页面再提交");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验库存是否足够和锁库存
|
||||||
|
List<SkuLockDTO> skuLockList = orderItems.stream()
|
||||||
|
.map(item -> SkuLockDTO.builder().skuId(item.getSkuId())
|
||||||
|
.count(item.getCount())
|
||||||
|
.orderToken(orderToken)
|
||||||
|
.build())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
Result lockResult = skuFeignService.lockStock(skuLockList);
|
||||||
|
|
||||||
|
if (!Result.success().getCode().equals(lockResult.getCode())) {
|
||||||
|
throw new BizException(Result.failed().getMsg());
|
||||||
|
}
|
||||||
|
// TCC模式创建订单(状态:待支付)
|
||||||
|
OmsOrder order = seataTccOrderService.prepareSubmitOrder(null, submitDTO);
|
||||||
|
// 将订单放入延时队列,超时未支付由交换机order.exchange切换到死信队列完成系统自动关单
|
||||||
|
log.info("订单超时取消RabbitMQ消息发送,订单SN:{}", orderToken);
|
||||||
|
rabbitTemplate.convertAndSend("order.exchange", "order.create", orderToken);
|
||||||
|
|
||||||
|
OrderSubmitVO submitVO = new OrderSubmitVO();
|
||||||
|
submitVO.setOrderId(order.getId());
|
||||||
|
submitVO.setOrderSn(order.getOrderSn());
|
||||||
|
log.info("订单提交响应:{}", submitVO.toString());
|
||||||
|
return submitVO;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单支付
|
* 订单支付
|
||||||
@ -310,5 +368,4 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, OmsOrder> impleme
|
|||||||
page.setRecords(list);
|
page.setRecords(list);
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
package com.youlai.mall.oms.tcc.idempotent;
|
||||||
|
|
||||||
|
import com.google.common.collect.HashBasedTable;
|
||||||
|
import com.google.common.collect.Table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author DaniR
|
||||||
|
* @Description TCC幂等工具类
|
||||||
|
* @Date 2021/7/15 20:38
|
||||||
|
**/
|
||||||
|
public class IdempotentUtil {
|
||||||
|
|
||||||
|
private static Table<Class<?>,String,Long> map= HashBasedTable.create();
|
||||||
|
|
||||||
|
public static void addMarker(Class<?> clazz,String xid,Long marker){
|
||||||
|
map.put(clazz,xid,marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Long getMarker(Class<?> clazz,String xid){
|
||||||
|
return map.get(clazz,xid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void removeMarker(Class<?> clazz,String xid){
|
||||||
|
map.remove(clazz,xid);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.youlai.mall.oms.tcc.service;
|
||||||
|
|
||||||
|
import com.youlai.mall.oms.pojo.dto.OrderSubmitDTO;
|
||||||
|
import com.youlai.mall.oms.pojo.entity.OmsOrder;
|
||||||
|
import io.seata.rm.tcc.api.BusinessActionContext;
|
||||||
|
import io.seata.rm.tcc.api.BusinessActionContextParameter;
|
||||||
|
import io.seata.rm.tcc.api.LocalTCC;
|
||||||
|
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
|
||||||
|
|
||||||
|
@LocalTCC
|
||||||
|
public interface SeataTccOrderService {
|
||||||
|
|
||||||
|
@TwoPhaseBusinessAction(name = "prepareSubmitOrder", commitMethod = "commitSubmitOrder", rollbackMethod = "rollbackSubmitOrder")
|
||||||
|
OmsOrder prepareSubmitOrder(BusinessActionContext businessActionContext,
|
||||||
|
@BusinessActionContextParameter(paramName = "orderSubmitDTO") OrderSubmitDTO orderSubmitDTO);
|
||||||
|
|
||||||
|
boolean commitSubmitOrder(BusinessActionContext businessActionContext);
|
||||||
|
|
||||||
|
boolean rollbackSubmitOrder(BusinessActionContext businessActionContext);
|
||||||
|
}
|
@ -0,0 +1,104 @@
|
|||||||
|
package com.youlai.mall.oms.tcc.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.youlai.common.web.util.JwtUtils;
|
||||||
|
import com.youlai.mall.oms.enums.OrderStatusEnum;
|
||||||
|
import com.youlai.mall.oms.enums.OrderTypeEnum;
|
||||||
|
import com.youlai.mall.oms.pojo.dto.OrderItemDTO;
|
||||||
|
import com.youlai.mall.oms.pojo.dto.OrderSubmitDTO;
|
||||||
|
import com.youlai.mall.oms.pojo.entity.OmsOrder;
|
||||||
|
import com.youlai.mall.oms.pojo.entity.OmsOrderItem;
|
||||||
|
import com.youlai.mall.oms.service.IOrderItemService;
|
||||||
|
import com.youlai.mall.oms.service.IOrderService;
|
||||||
|
import com.youlai.mall.oms.tcc.idempotent.IdempotentUtil;
|
||||||
|
import com.youlai.mall.oms.tcc.service.SeataTccOrderService;
|
||||||
|
import io.seata.rm.tcc.api.BusinessActionContext;
|
||||||
|
import io.seata.rm.tcc.api.LocalTCC;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@LocalTCC
|
||||||
|
public class SeataTccOrderServiceImpl implements SeataTccOrderService {
|
||||||
|
@Autowired
|
||||||
|
private IOrderService orderService;
|
||||||
|
@Autowired
|
||||||
|
private IOrderItemService orderItemService;
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public OmsOrder prepareSubmitOrder(BusinessActionContext businessActionContext, OrderSubmitDTO orderSubmitDTO) {
|
||||||
|
log.info("==========创建 订单 第一阶段,事务组Xid:{} ==========", businessActionContext.getXid());
|
||||||
|
List<OrderItemDTO> orderItems = orderSubmitDTO.getOrderItems();
|
||||||
|
String orderToken = orderSubmitDTO.getOrderToken();
|
||||||
|
// 创建订单(状态:待支付)
|
||||||
|
OmsOrder order = new OmsOrder();
|
||||||
|
order.setOrderSn(orderToken)
|
||||||
|
.setStatus(OrderStatusEnum.PENDING_PAYMENT.getCode())
|
||||||
|
.setSourceType(OrderTypeEnum.APP.getCode())
|
||||||
|
.setMemberId(JwtUtils.getUserId())
|
||||||
|
.setRemark(orderSubmitDTO.getRemark())
|
||||||
|
.setPayAmount(orderSubmitDTO.getPayAmount())
|
||||||
|
.setTotalQuantity(orderItems.stream().map(item -> item.getCount()).reduce(0, (x, y) -> x + y))
|
||||||
|
.setTotalAmount(orderItems.stream().map(item -> item.getPrice() * item.getCount()).reduce(0l, (x, y) -> x + y))
|
||||||
|
.setGmtCreate(new Date());
|
||||||
|
orderService.save(order);
|
||||||
|
// 创建订单商品
|
||||||
|
List<OmsOrderItem> orderItemList = orderItems.stream().map(item -> OmsOrderItem.builder()
|
||||||
|
.orderId(order.getId())
|
||||||
|
.skuId(item.getSkuId())
|
||||||
|
.skuName(item.getSkuName())
|
||||||
|
.skuPrice(item.getPrice())
|
||||||
|
.skuPic(item.getPic())
|
||||||
|
.skuQuantity(item.getCount())
|
||||||
|
.skuTotalPrice(item.getCount() * item.getPrice())
|
||||||
|
.skuCode(item.getSkuCode())
|
||||||
|
.build()).collect(Collectors.toList());
|
||||||
|
orderItemService.saveBatch(orderItemList);
|
||||||
|
log.info("保存订单:{} 成功", order.getOrderSn());
|
||||||
|
IdempotentUtil.addMarker(getClass(), businessActionContext.getXid(), System.currentTimeMillis());
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public boolean commitSubmitOrder(BusinessActionContext businessActionContext) {
|
||||||
|
log.info("==========已经完成Confirm-->创建 订单 第二阶段提交,事务组Xid:{} ==========", businessActionContext.getXid());
|
||||||
|
if (Objects.isNull(IdempotentUtil.getMarker(getClass(), businessActionContext.getXid()))) {
|
||||||
|
log.info("已执行过Confirm阶段");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
IdempotentUtil.removeMarker(getClass(), businessActionContext.getXid());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public boolean rollbackSubmitOrder(BusinessActionContext businessActionContext) {
|
||||||
|
log.info("======================rollbackSubmitOrder========================");
|
||||||
|
if (Objects.isNull(IdempotentUtil.getMarker(getClass(), businessActionContext.getXid()))) {
|
||||||
|
log.info("已执行过rollback阶段");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
JSONObject jsonObject = (JSONObject) businessActionContext.getActionContext("orderSubmitDTO");
|
||||||
|
OrderSubmitDTO orderSubmitDTO = new OrderSubmitDTO();
|
||||||
|
BeanUtil.copyProperties(jsonObject, orderSubmitDTO);
|
||||||
|
OmsOrder omsOrder = orderService.getOne(new LambdaQueryWrapper<OmsOrder>().eq(OmsOrder::getOrderSn, orderSubmitDTO.getOrderToken()));
|
||||||
|
if (Objects.nonNull(omsOrder)) {
|
||||||
|
orderItemService.remove(new LambdaQueryWrapper<OmsOrderItem>().eq(OmsOrderItem::getOrderId, omsOrder.getId()));
|
||||||
|
orderService.deleteOrder(omsOrder.getId());
|
||||||
|
}
|
||||||
|
IdempotentUtil.removeMarker(getClass(), businessActionContext.getXid());
|
||||||
|
log.info("========== 已经完成Cancle-->第二阶段回滚,事务组Xid:{} ==========" + businessActionContext.getXid());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,11 @@ public interface IPmsSkuService extends IService<PmsSku> {
|
|||||||
*/
|
*/
|
||||||
boolean lockStock(List<SkuLockDTO> list);
|
boolean lockStock(List<SkuLockDTO> list);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 锁定库存
|
||||||
|
*/
|
||||||
|
boolean lockStockTcc(List<SkuLockDTO> list);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解锁库存
|
* 解锁库存
|
||||||
*/
|
*/
|
||||||
|
@ -13,6 +13,8 @@ import com.youlai.mall.pms.pojo.entity.PmsSku;
|
|||||||
import com.youlai.mall.pms.pojo.dto.SkuDTO;
|
import com.youlai.mall.pms.pojo.dto.SkuDTO;
|
||||||
import com.youlai.mall.pms.pojo.dto.SkuLockDTO;
|
import com.youlai.mall.pms.pojo.dto.SkuLockDTO;
|
||||||
import com.youlai.mall.pms.service.IPmsSkuService;
|
import com.youlai.mall.pms.service.IPmsSkuService;
|
||||||
|
import com.youlai.mall.pms.tcc.service.SeataTccSkuService;
|
||||||
|
import io.seata.spring.annotation.GlobalTransactional;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.redisson.api.RLock;
|
import org.redisson.api.RLock;
|
||||||
@ -35,9 +37,21 @@ public class PmsSkuServiceImpl extends ServiceImpl<PmsSkuMapper, PmsSku> impleme
|
|||||||
|
|
||||||
private RedissonClient redissonClient;
|
private RedissonClient redissonClient;
|
||||||
|
|
||||||
|
private SeataTccSkuService seataTccSkuService;
|
||||||
|
|
||||||
|
@GlobalTransactional
|
||||||
|
@Override
|
||||||
|
public boolean lockStockTcc(List<SkuLockDTO> skuLockList) {
|
||||||
|
|
||||||
|
seataTccSkuService.prepareSkuLockList(null,skuLockList);
|
||||||
|
String orderToken = skuLockList.get(0).getOrderToken();
|
||||||
|
redisTemplate.opsForValue().set(LOCKED_STOCK_PREFIX + orderToken, JSONUtil.toJsonStr(skuLockList));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 创建订单时锁定库存
|
* 创建订单时锁定库存
|
||||||
*/
|
*/
|
||||||
|
@GlobalTransactional
|
||||||
@Override
|
@Override
|
||||||
public boolean lockStock(List<SkuLockDTO> skuLockList) {
|
public boolean lockStock(List<SkuLockDTO> skuLockList) {
|
||||||
log.info("=======================创建订单,开始锁定商品库存=======================");
|
log.info("=======================创建订单,开始锁定商品库存=======================");
|
||||||
@ -45,7 +59,7 @@ public class PmsSkuServiceImpl extends ServiceImpl<PmsSkuMapper, PmsSku> impleme
|
|||||||
if (CollectionUtil.isEmpty(skuLockList)) {
|
if (CollectionUtil.isEmpty(skuLockList)) {
|
||||||
throw new BizException("锁定的商品列表为空");
|
throw new BizException("锁定的商品列表为空");
|
||||||
}
|
}
|
||||||
|
//prepareSkuLockList(null, skuLockList);
|
||||||
// 锁定商品
|
// 锁定商品
|
||||||
skuLockList.forEach(item -> {
|
skuLockList.forEach(item -> {
|
||||||
RLock lock = redissonClient.getLock(LOCK_SKU_PREFIX + item.getSkuId()); // 获取商品的分布式锁
|
RLock lock = redissonClient.getLock(LOCK_SKU_PREFIX + item.getSkuId()); // 获取商品的分布式锁
|
||||||
@ -84,6 +98,7 @@ public class PmsSkuServiceImpl extends ServiceImpl<PmsSkuMapper, PmsSku> impleme
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单超时关单解锁库存
|
* 订单超时关单解锁库存
|
||||||
*/
|
*/
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
package com.youlai.mall.pms.tcc.idempotent;
|
||||||
|
|
||||||
|
import com.google.common.collect.HashBasedTable;
|
||||||
|
import com.google.common.collect.Table;
|
||||||
|
/**
|
||||||
|
* @Author DaniR
|
||||||
|
* @Description TCC幂等工具类
|
||||||
|
* @Date 2021/7/15 20:38
|
||||||
|
**/
|
||||||
|
public class IdempotentUtil {
|
||||||
|
|
||||||
|
private static Table<Class<?>,String,Long> map= HashBasedTable.create();
|
||||||
|
|
||||||
|
public static void addMarker(Class<?> clazz,String xid,Long marker){
|
||||||
|
map.put(clazz,xid,marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Long getMarker(Class<?> clazz,String xid){
|
||||||
|
return map.get(clazz,xid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void removeMarker(Class<?> clazz,String xid){
|
||||||
|
map.remove(clazz,xid);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package com.youlai.mall.pms.tcc.service;
|
||||||
|
|
||||||
|
import com.youlai.mall.pms.pojo.dto.SkuLockDTO;
|
||||||
|
import io.seata.rm.tcc.api.BusinessActionContext;
|
||||||
|
import io.seata.rm.tcc.api.BusinessActionContextParameter;
|
||||||
|
import io.seata.rm.tcc.api.LocalTCC;
|
||||||
|
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@LocalTCC
|
||||||
|
public interface SeataTccSkuService {
|
||||||
|
|
||||||
|
@TwoPhaseBusinessAction(name = "prepareSkuLockList", commitMethod = "commitSkuLockList", rollbackMethod = "rollbackSkuLockList")
|
||||||
|
boolean prepareSkuLockList(BusinessActionContext businessActionContext,
|
||||||
|
@BusinessActionContextParameter(paramName = "skuLockList") List<SkuLockDTO> skuLockList);
|
||||||
|
|
||||||
|
boolean commitSkuLockList(BusinessActionContext businessActionContext);
|
||||||
|
|
||||||
|
boolean rollbackSkuLockList(BusinessActionContext businessActionContext);
|
||||||
|
}
|
@ -0,0 +1,110 @@
|
|||||||
|
package com.youlai.mall.pms.tcc.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
|
import com.alibaba.fastjson.JSONArray;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
|
import com.youlai.common.web.exception.BizException;
|
||||||
|
import com.youlai.mall.pms.pojo.dto.SkuLockDTO;
|
||||||
|
import com.youlai.mall.pms.pojo.entity.PmsSku;
|
||||||
|
import com.youlai.mall.pms.service.IPmsSkuService;
|
||||||
|
import com.youlai.mall.pms.tcc.idempotent.IdempotentUtil;
|
||||||
|
import com.youlai.mall.pms.tcc.service.SeataTccSkuService;
|
||||||
|
import io.seata.rm.tcc.api.BusinessActionContext;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.redisson.api.RLock;
|
||||||
|
import org.redisson.api.RedissonClient;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static com.youlai.mall.pms.common.constant.PmsConstants.LOCK_SKU_PREFIX;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class SeataTccSkuServiceImpl implements SeataTccSkuService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IPmsSkuService iPmsSkuService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedissonClient redissonClient;
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public boolean prepareSkuLockList(BusinessActionContext businessActionContext, List<SkuLockDTO> skuLockList) {
|
||||||
|
log.info("=======================创建订单,开始锁定商品库存=======================");
|
||||||
|
//防幂等
|
||||||
|
if (Objects.nonNull(IdempotentUtil.getMarker(getClass(), businessActionContext.getXid()))) {
|
||||||
|
log.info("已执行过try阶段");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
log.info("锁定商品信息:{}", skuLockList.toString());
|
||||||
|
if (CollectionUtil.isEmpty(skuLockList)) {
|
||||||
|
throw new BizException("锁定的商品列表为空");
|
||||||
|
}
|
||||||
|
// 锁定商品
|
||||||
|
skuLockList.forEach(item -> {
|
||||||
|
RLock lock = redissonClient.getLock(LOCK_SKU_PREFIX + item.getSkuId()); // 获取商品的分布式锁
|
||||||
|
lock.lock();
|
||||||
|
boolean result = iPmsSkuService.update(new LambdaUpdateWrapper<PmsSku>()
|
||||||
|
.setSql("locked_stock = locked_stock + " + item.getCount())
|
||||||
|
.eq(PmsSku::getId, item.getSkuId())
|
||||||
|
.apply("stock - locked_stock >= {0}", item.getCount())
|
||||||
|
);
|
||||||
|
item.setLocked(result);
|
||||||
|
lock.unlock();
|
||||||
|
});
|
||||||
|
// 锁定失败的商品集合
|
||||||
|
List<SkuLockDTO> unlockSkuList = skuLockList.stream().filter(item -> !item.getLocked()).collect(Collectors.toList());
|
||||||
|
if (CollectionUtil.isNotEmpty(unlockSkuList)) {
|
||||||
|
// 恢复已被锁定的库存
|
||||||
|
List<SkuLockDTO> lockSkuList = skuLockList.stream().filter(SkuLockDTO::getLocked).collect(Collectors.toList());
|
||||||
|
lockSkuList.forEach(item ->
|
||||||
|
iPmsSkuService.update(new LambdaUpdateWrapper<PmsSku>()
|
||||||
|
.eq(PmsSku::getId, item.getSkuId())
|
||||||
|
.setSql("locked_stock = locked_stock - " + item.getCount()))
|
||||||
|
);
|
||||||
|
// 提示订单哪些商品库存不足
|
||||||
|
List<Long> ids = unlockSkuList.stream().map(SkuLockDTO::getSkuId).collect(Collectors.toList());
|
||||||
|
throw new BizException("商品" + ids.toString() + "库存不足");
|
||||||
|
}
|
||||||
|
IdempotentUtil.addMarker(getClass(), businessActionContext.getXid(), System.currentTimeMillis());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public boolean commitSkuLockList(BusinessActionContext businessActionContext) {
|
||||||
|
log.info("=====================commitSkuLockList 成功=========================");
|
||||||
|
if (Objects.isNull(IdempotentUtil.getMarker(getClass(), businessActionContext.getXid()))) {
|
||||||
|
log.info(businessActionContext.getXid() + ": 已执行过commit阶段");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
IdempotentUtil.removeMarker(getClass(), businessActionContext.getXid());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Override
|
||||||
|
public boolean rollbackSkuLockList(BusinessActionContext businessActionContext) {
|
||||||
|
log.info("======================rollbackSkuLockList========================");
|
||||||
|
if (Objects.isNull(IdempotentUtil.getMarker(getClass(), businessActionContext.getXid()))) {
|
||||||
|
log.info(businessActionContext.getXid() + ": 已执行过rollback阶段");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
JSONArray jsonObjectList = (JSONArray) businessActionContext.getActionContext("skuLockList");
|
||||||
|
List<SkuLockDTO> skuLockList = JSONObject.parseArray(jsonObjectList.toJSONString(), SkuLockDTO.class);
|
||||||
|
skuLockList.forEach(item ->
|
||||||
|
iPmsSkuService.update(new LambdaUpdateWrapper<PmsSku>()
|
||||||
|
.eq(PmsSku::getId, item.getSkuId())
|
||||||
|
.setSql("locked_stock = locked_stock - " + item.getCount()))
|
||||||
|
);
|
||||||
|
IdempotentUtil.removeMarker(getClass(), businessActionContext.getXid());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user