一、实体类与controller层
@Data
public class OrderVo {
@ApiModelProperty(value = "使用预生产订单号防重")
private String orderNo;
@ApiModelProperty(value = "用户id")
private Long userId;
@ApiModelProperty(value = "收货人姓名")
private String receiverName;
@ApiModelProperty(value = "收货人电话")
private String receiverPhone;
@ApiModelProperty(value = "下单选中的优惠券id")
private Long couponId;
}
@ApiOperation("生成订单") //swagger注解
@PostMapping("auth/submitOrder")
public Result submitOrder(@RequestBody OrderVo orderVo) {
Long orderId = orderInfoService.submitOrder(orderVo);
return Result.ok(orderId);
}
接口实现类实现细节:
第一步:获取用户, 确定用户的订单
第二步:确保订单的唯一性, 订单不能重复提交,重复提交验证,通过redis + lua脚本进行判断
第三步:lua脚本保证原子性操作
第四步:验证库存 并且 锁定库存
示例:比如仓库有5个耳机,我想买2个 验证库存,查询仓库里面是是否有充足耳机 若库存充足,库存锁定 2 (目前没有真正减库存)
代码中的lua脚本:
String script = "if(redis.call('get', KEYS[1]) == ARGV[1]) then return redis.call('del', KEYS[1]) else return 0 end";
解释: 如果redis有相同orderNo,表示正常提交订单,把redis的orderNo删除。
传入
具体代码:
@Override
public Long ordersSubmit(OrderVo orderVo) {
//获取用户,确定用户的订单
Long userId = AuthContextHolder.getUserId();
orderVo.setUserId(userId);
//确保订单的唯一性,
String orderNo = orderVo.getOrderNo();
if (StringUtils.isEmpty(orderNo)) {
throw new SsyxException(ResultCodeEnum.ILLEGAL_REQUEST);
}
// 拿着orderNo 到 redis进行查询,
String script = "if(redis.call('get', KEYS[1]) == ARGV[1]) then return redis.call('del', KEYS[1]) else return 0 end";
// 如果redis有相同orderNo,表示正常提交订单,把redis的orderNo删除
Boolean b = (Boolean) redisTemplate.execute(new DefaultRedisScript(script, boolean.class),
Arrays.asList(RedisConst.ORDER_REPEAT + orderNo), orderNo);
if (!b) {
//重复提交抛出异常
throw new SsyxException(ResultCodeEnum.REPEAT_SUBMIT);
}
//第三步 远程调用cartFeignClient ,验证库存 并且 锁定库存
List<CartInfo> cartInfoList = cartFeignClient.getCartCheckedProduct(userId);
// 获取普通商品
List<CartInfo> commonList = cartInfoList.stream()
.filter(cartInfo -> cartInfo.getSkuType() == SkuType.COMMON.getCode())
.collect(Collectors.toList());
//3、把获取购物车里面普通类型商品list集合,List<CartInfo>转换List<SkuStockLockVo>
if (!CollectionUtils.isEmpty(commonList)) {
List<SkuStockLockVo> skuStockLockVoList = commonList.stream().map(common -> {
SkuStockLockVo skuStockLockVo = new SkuStockLockVo();
BeanUtils.copyProperties(common, skuStockLockVo);
return skuStockLockVo;
}).collect(Collectors.toList());
//4、远程调用service-product模块实现锁定商品
// 验证库存并锁定库存,保证具备原子性
Boolean isLockSuccess =
productFeignClient.checkLock(skuStockLockVoList, orderNo);
if(!isLockSuccess) {//库存锁定失败
throw new SsyxException(ResultCodeEnum.ORDER_STOCK_FALL);
}
}
//第四步 下单步骤
//1 向两张表添加数据
// order_info 和 order_item
Long orderId = this.saveOrder(orderVo,cartInfoList);
//下单完成,删除购物车记录
//发送mq消息
rabbitService.sendMessage(MqConst.EXCHANGE_ORDER_DIRECT,
MqConst.ROUTING_DELETE_CART,orderVo.getUserId());
// 返回订单Id
return orderId;
}@Data
public class CartInfo extends BaseEntity {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "用户id")
private Long userId;
@ApiModelProperty(value = "分类id")
private Long categoryId;
@ApiModelProperty(value = "商品类型:0->普通商品 1->秒杀商品")
private Integer skuType;
@ApiModelProperty(value = "是否新人专享:0->否;1->是")
private Integer isNewPerson;
@ApiModelProperty(value = "sku名称 (冗余)")
private String skuName;
@ApiModelProperty(value = "skuid")
private Long skuId;
@ApiModelProperty(value = "放入购物车时价格")
private BigDecimal cartPrice;
@ApiModelProperty(value = "数量")
private Integer skuNum;
@ApiModelProperty(value = "限购个数")
private Integer perLimit;
@ApiModelProperty(value = "图片文件")
private String imgUrl;
@ApiModelProperty(value = "isChecked")
private Integer isChecked;
@ApiModelProperty(value = "状态")
private Integer status;
@ApiModelProperty(value = "秒杀开始时间")
@JsonFormat(pattern = "HH:mm:ss")
private Date startTime;
@ApiModelProperty(value = "秒杀结束时间")
@JsonFormat(pattern = "HH:mm:ss")
private Date endTime;
@ApiModelProperty(value = "仓库")
private Long wareId;
}@Data
public class SkuStockLockVo implements Serializable {
@ApiModelProperty(value = "skuId")
private Long skuId;
@ApiModelProperty(value = "sku个数")
private Integer skuNum;
@ApiModelProperty(value = "是否锁定")
private Boolean isLock = false;
}
远程服务 CartFeignClient :
@FeignClient("service-cart")
public interface CartFeignClient {
@GetMapping("/cart/getCartCheckedList/{userId}")
List<CartInfo> getCartCheckedProduct(Long userId);
}
@GetMapping("/cart/getCartCheckedList/{userId}")
public List<CartInfo> getCartCheckedList(@PathVariable("userId") Long userId) {
return cartInfoService.getCartCheckedProduct(userId);
}
//获取当前用户购物车选中购物项
@Override
public List<CartInfo> getCartCheckedProduct(Long userId) {
String cartKey = this.getCartKey(userId);
BoundHashOperations<String,String,CartInfo> boundHashOperations =
redisTemplate.boundHashOps(cartKey);
List<CartInfo> cartInfoProduct = boundHashOperations.values();
//isChecked = 1表示选中的商品
List<CartInfo> cartInfoList = cartInfoList.stream()
.filter(cartInfo -> {
return cartInfo.getIsChecked().intValue() == 1;
}).collect(Collectors.toList());
return cartInfoList ;
}
ProductFeginClient 服务具体实现
@FeignClient(value = "service-product")
public interface ProductFeignClient {
@PostMapping("product/checkAndLock/{orderNo}")
public Boolean checkLock(List<SkuStockLockVo> skuStockLockVoList, @PathVariable("orderNo") String orderNo);
}
controller层: controller中的请求接口要与ProductFeignClient的保持一致
@ApiOperation(value = "锁定库存")
@PostMapping("product/checkAndLock/{orderNo}")
public Boolean checkAndLock(@RequestBody List<SkuStockLockVo> skuStockLockVoList,
@PathVariable String orderNo) {
return skuInfoService.checkLock(skuStockLockVoList,orderNo);
}
//验证和锁定库存
@Override
public Boolean checkLock(List<SkuStockLockVo> skuStockLockVoList,
String orderNo) {
//1 判断skuStockLockVoList集合是否为空
if (CollectionUtils.isEmpty(skuStockLockVoList)) {
throw new SsyxException(ResultCodeEnum.DATA_ERROR);
}
//2 遍历skuStockLockVoList得到每个商品,验证库存并锁定库存,具备原子性
skuStockLockVoList.stream().forEach(skuStockLockVo -> {
this.checkLock(skuStockLockVo);
});
//3 只要有一个商品锁定失败,所有锁定成功的商品都解锁
boolean flag = skuStockLockVoList.stream()
.anyMatch(skuStockLockVo -> !skuStockLockVo.getIsLock());
if (flag) {
//所有锁定成功的商品都解锁
skuStockLockVoList.stream().filter(SkuStockLockVo::getIsLock)
.forEach(skuStockLockVo -> {
baseMapper.unlockStock(skuStockLockVo.getSkuId(),
skuStockLockVo.getSkuNum());
});
//返回失败的状态
return false;
}
//4 如果所有商品都锁定成功了,redis缓存相关数据,为了方便后面解锁和减库存
redisTemplate.opsForValue()
.set(RedisConst.SROCK_INFO + orderNo, skuStockLockVoList);
return true;
}