一、实体类与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;
}