尚品汇-引入库存系统、校验库存、异步编排优化下单(四十一)

目录:

(1)如何解决用户利用浏览器回退重复提交订单?

(2)验库存与验证价格

(3)查询仓库数量,进行校验

(4)优化下单

(1)如何解决用户利用浏览器回退重复提交订单?

在进入结算页面时,生成一个结算流水号,然后保存到结算页面的隐藏元素中,每次用户提交都检查该流水号与页面提交的是否相符,订单保存以后把后台的流水号删除掉。那么第二次用户用同一个页面提交的话流水号就会匹配失败,无法重复保存订单。 

进入结算页的时候生成一个流水号:保证结算的唯一 

 

修改结算页增加流水号的生成

OrderService接口
/**
 * 生产流水号
 * @param userId
 * @return
 */
String getTradeNo(String userId);

/**
 * 比较流水号
 * @param userId 获取缓存中的流水号
 * @param tradeCodeNo   页面传递过来的流水号
 * @return
 */
boolean checkTradeCode(String userId, String tradeCodeNo);


/**
 * 删除流水号
 * @param userId
 */
void deleteTradeNo(String userId);

实现类

@Autowired
private RedisTemplate redisTemplate;

@Override
public String getTradeNo(String userId) {
    // 定义key
    String tradeNoKey = "user:" + userId + ":tradeCode";
    // 定义一个流水号
    String tradeNo = UUID.randomUUID().toString().replace("-", "");
    redisTemplate.opsForValue().set(tradeNoKey, tradeNo);
    return tradeNo;
}

@Override
public boolean checkTradeCode(String userId, String tradeCodeNo) {
    // 定义key
    String tradeNoKey = "user:" + userId + ":tradeCode";
    String redisTradeNo = (String) redisTemplate.opsForValue().get(tradeNoKey);
    return tradeCodeNo.equals(redisTradeNo);
}

@Override
public void deleteTradeNo(String userId) {
    // 定义key
    String tradeNoKey = "user:" + userId + ":tradeCode";
    // 删除数据
    redisTemplate.delete(tradeNoKey);
}

在OrderApiController类trade方法添加交易流水号

// 获取流水号

String tradeNo = orderService.getTradeNo(userId);

result.put("tradeNo", tradeNo);

在OrderApiController控制器中实现

// 获取前台页面的流水号

  String tradeNo = request.getParameter("tradeNo");

  // 调用服务层的比较方法

  boolean flag = orderService.checkTradeCode(userId, tradeNo);

  if (!flag) {
    // 比较失败!
    return Result.fail().message("不能重复提交订单!");
}
//  删除流水号
orderService.deleteTradeNo(userId);

第二次提交:

(2)验库存与验证价格

通过restful接口查询商品是否有库存

    一般电商系统的商品库存,都不由电商系统本身来管理,由另外一套仓库管理系统,或者进销存系统来管理,电商系统通过第三方接口调用该系统。

    由于库管系统可能是异构的系统,所以不在微服务体系之内。只支持restful风格的webservice调用和消息队列的调用。

导入项目ware-manage项目

详情查看库存管理系统文档,把资料中的ware-manage项目直接放入到项目模块目录下。

打开库存项目

点击OK.

修改一下plus的版本,我们父工程用的高版本,高版本不兼容低版本,这里用的低版本的方法, 不修改启动会保存

修改配置文件:

 

导入sql 

仓库表 

我们做的不是B2B2C的项目,不会存在多家商家,这里只是我们自己的项目,可能只是仓库不在同一位置。需要这两个仓库对商品的订单进行处理,根据这个来拆

库存工作单表:

库存工作单明细表:

 sku与仓库关联表:

填写库存信息!

可以访问库存系统:http://localhost:9001/index

验证库存:

添加商品库存的时候,商品的库存应该选择同一个库存!

(3)查询仓库数量,进行校验

现在验证库存数量方法

在orderService接口中定义验库存接口

/**
 * 验证库存
 * @param skuId
 * @param skuNum
 * @return
 */
boolean checkStock(Long skuId, Integer skuNum);

 

实现类OrderServiceImpl
//读取nacos的配置
@Value("${ware.url}")
private String WARE_URL;

@Override
public boolean checkStock(Long skuId, Integer skuNum) {
    // 远程调用http://localhost:9001/hasStock?skuId=10221&num=2
    String result = HttpClientUtil.doGet(WARE_URL + "/hasStock?skuId=" + skuId + "&num=" + skuNum);
    return "1".equals(result);
}

submitOrder中增加 方法

@Autowired
private ProductFeignClient productFeignClient;

/**
 * 提交订单
 * @param orderInfo
 * @param request
 * @return
 */
@PostMapping("auth/submitOrder")
public Result submitOrder(@RequestBody OrderInfo orderInfo, HttpServletRequest request) {
    // 获取到用户Id
    String userId = AuthContextHolder.getUserId(request);
    orderInfo.setUserId(Long.parseLong(userId));

    // 获取前台页面的流水号
    String tradeNo = request.getParameter("tradeNo");

    // 调用服务层的比较方法
    boolean flag = orderService.checkTradeCode(userId, tradeNo);
    if (!flag) {
        // 比较失败!
        return Result.fail().message("不能重复提交订单!");
    }
    //  删除流水号
    orderService.deleteTradeNo(userId);

   







 // 验证库存:
    List<OrderDetail> orderDetailList = orderInfo.getOrderDetailList();
    for (OrderDetail orderDetail : orderDetailList) {

        // 验证库存:
        boolean result = orderService.checkStock(orderDetail.getSkuId(), orderDetail.getSkuNum());
        if (!result) {
            return Result.fail().message(orderDetail.getSkuName() + "库存不足!");
        }
        



// 验证价格:缓存跟MySql
        BigDecimal skuPrice = productFeignClient.getSkuPrice(orderDetail.getSkuId());
        if (orderDetail.getOrderPrice().compareTo(skuPrice) != 0) {
            // 重新查询价格!
            //  设置最新的价格  
            //获取选中的的列表,这个个方法里进行封信价格
       List<CartInfo> cartCheckedList = this.cartFeignClient.getCartCheckedList(userId);
     //  更新写入缓存:
cartCheckedList.forEach(cartInfo -> {
    this.redisTemplate.opsForHash().put(RedisConst.USER_KEY_PREFIX + userId + RedisConst.USER_CART_KEY_SUFFIX, cartInfo.getSkuId().toString(), cartInfo);
});
            return Result.fail().message(orderDetail.getSkuName() + "价格有变动!");
        }

 
    }


    // 验证通过,保存订单!
    Long orderId = orderService.saveOrderInfo(orderInfo);
    return Result.ok(orderId);
}

 

 

修改库存数量: 

该锁定库存:为99,剩余一个

 

页面购买2个:

 

价格更新测试

此时进行更新价格

缓存也进行了更改 

(4)优化下单

下单我们要校验库存与价格,请求比较多,时间比较长,我们可以通过异步编排的形式减少请求时间,异步编排我们前面已经学习过了,接下来怎么做呢

这里是循环一个一个的校验,时间肯定是慢的 

引入线程类

将service-item下面的config线程池配置类copy过来

package com.atguigu.gmall.order.config;


@Configuration
public class ThreadPoolConfig {

    /**
     *
     * public ThreadPoolExecutor(int corePoolSize,
     *                               int maximumPoolSize,
     *                               long keepAliveTime,
     *                               TimeUnit unit,
     *                               BlockingQueue<Runnable> workQueue,
     *                               ThreadFactory threadFactory,
     *                               RejectedExecutionHandler handler)
     * 构造函数的参数含义如下:
     *
     * corePoolSize:指定了线程池中的线程数量,它的数量决定了添加的任务是开辟新的线程去执行,还是放到workQueue任务队列中去;
     * maximumPoolSize:指定了线程池中的最大线程数量,这个参数会根据你使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量;
     * keepAliveTime:当线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内被销毁;
     * unit:keepAliveTime的单位
     * workQueue:任务队列,被添加到线程池中,但尚未被执行的任务;它一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列几种;
     * threadFactory:线程工厂,用于创建线程,一般用默认即可;
     * handler:拒绝策略;当任务太多来不及处理时,如何拒绝任务;
     * @return
     */
    @Bean
    public ThreadPoolExecutor threadPoolExecutor(){

        return new ThreadPoolExecutor(50, 500, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10000));
    }
}

调整下单类

@Autowired
private ThreadPoolExecutor threadPoolExecutor;
/**
 * 提交订单
 * @param orderInfo
 * @param request
 * @return
 */
@PostMapping("auth/submitOrder")
public Result submitOrder(@RequestBody OrderInfo orderInfo, HttpServletRequest request) {
    // 获取到用户Id
    String userId = AuthContextHolder.getUserId(request);
    orderInfo.setUserId(Long.parseLong(userId));

    // 获取前台页面的流水号
    String tradeNo = request.getParameter("tradeNo");

    // 调用服务层的比较方法
    boolean flag = orderService.checkTradeCode(userId, tradeNo);
    if (!flag) {
        // 比较失败!
        return Result.fail().message("不能重复提交订单!");
    }

    //  删除流水号
    orderService.deleteTradeNo(userId);

    List<String> errorList = new ArrayList<>();


    List<CompletableFuture> futureList = new ArrayList<>();


    // 验证库存:
    List<OrderDetail> orderDetailList = orderInfo.getOrderDetailList();
    for (OrderDetail orderDetail : orderDetailList) {
        CompletableFuture<Void> checkStockCompletableFuture = CompletableFuture.runAsync(() -> {
            // 验证库存:
            boolean result = orderService.checkStock(orderDetail.getSkuId(), orderDetail.getSkuNum());
            if (!result) {
                errorList.add(orderDetail.getSkuName() + "库存不足!");
            }
        }, threadPoolExecutor);
        futureList.add(checkStockCompletableFuture);

        CompletableFuture<Void> checkPriceCompletableFuture = CompletableFuture.runAsync(() -> {
            // 验证价格:
            BigDecimal skuPrice = productFeignClient.getSkuPrice(orderDetail.getSkuId());
            if (orderDetail.getOrderPrice().compareTo(skuPrice) != 0) {
                // 重新查询价格!
List<CartInfo> cartCheckedList = this.cartFeignClient.getCartCheckedList(userId);
//  写入缓存:
cartCheckedList.forEach(cartInfo -> {
    this.redisTemplate.opsForHash().put(RedisConst.USER_KEY_PREFIX + userId + RedisConst.USER_CART_KEY_SUFFIX, cartInfo.getSkuId().toString(), cartInfo);
});
                errorList.add(orderDetail.getSkuName() + "价格有变动!");
            }
        }, threadPoolExecutor);
        futureList.add(checkPriceCompletableFuture);
    }

    //合并线程
    CompletableFuture.allOf(futureList.toArray(new CompletableFuture[futureList.size()])).join();


//错误集合信息,判断是否下订单
    if(errorList.size() > 0) {
        return Result.fail().message(StringUtils.join(errorList, ","));
    }

    // 验证通过,保存订单!
    Long orderId = orderService.saveOrderInfo(orderInfo);
    return Result.ok(orderId);
}

  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

喵俺第一专栏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值