商城电商day13一、订单业务简介

时间:2024-10-04 07:12:05

在这里插入图片描述

订单业务在整个电商平台中处于核心位置,也是比较复杂的一块业务。是把“物”变为“钱”的一个中转站。
整个订单模块一共分四部分组成:

  1. 结算
  2. 下单
  3. 对接支付服务
  4. 对接库存管理系统

入口:购物车点击计算按钮 ,结算必须要登录!
在这里插入图片描述
分析页面需要的数据:
1、 需要用户地址信息
2、 购物车中选择的商品列表
3、 用户地址信息在service-user模块,购物车信息在service-cart模块,所以我们要在相应模块提供api接口,通过feign client调用获取数据

2.1在 service-user模块获取地址列表

常规

2.2 在service-cart模块获取选中商品数据

分析用到那几张表
cart_info(购物车表) user_id sku_id
SQL语句

控制层

业务层Dao层
List getCartCheckedList(String userId);

难点

1 定义key user:userId:cart 获取redis中的所有商品
2 // 获取选中的商品!遍历所有条件判断加入集合
if (().intValue() == 1) {
(cartInfo);
}
3 //返回集合

@Override
public List<CartInfo> getCartCheckedList(String userId) {
    List<CartInfo> cartInfoList = new ArrayList<>();

    // 定义key user:userId:cart
    String cartKey = this.getCartKey(userId);
    List<CartInfo> cartCachInfoList = redisTemplate.opsForHash().values(cartKey);
    if (null != cartCachInfoList && cartCachInfoList.size() > 0) {
        for (CartInfo cartInfo : cartCachInfoList) {
            // 获取选中的商品!
            if (cartInfo.getIsChecked().intValue() == 1) {
                cartInfoList.add(cartInfo);
            }
        }
    }
    return cartInfoList;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

2.3 搭建service-order模块

2.3.4 订单的数据结构

orderInfo :订单表
orderDetail :订单明细

2.3.5 接口封装OrderApiController

确认订单
	1 // 获取到用户Id
	2 //远程调用 获取用户地址
	3 // 先得到用户想要购买的商品!
	        4 // 声明一个集合来存储订单明细  订单需要放每个用户选中的物品集合详情
   			 ArrayList<OrderDetail> detailArrayList = new ArrayList<>();
			循环添加完所有商品后
	5 // 计算总金额
    	 6 // 保存总金额
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

```java
package com.atguigu.gmall.order.controller;
@RestController
@RequestMapping("api/order")
public class OrderApiController {

    @Autowired
    private UserFeignClient userFeignClient;

    @Autowired
    private CartFeignClient cartFeignClient;


    /**
     * 确认订单
     * @param request
     * @return
     */
    @GetMapping("auth/trade")
    public Result<Map<String, Object>> trade(HttpServletRequest request) {
        // 获取到用户Id
        String userId = AuthContextHolder.getUserId(request);

        //获取用户地址
        List<UserAddress> userAddressList = userFeignClient.findUserAddressListByUserId(userId);

        // 渲染送货清单
        // 先得到用户想要购买的商品!
        List<CartInfo> cartInfoList = cartFeignClient.getCartCheckedList(userId);
        // 声明一个集合来存储订单明细
        ArrayList<OrderDetail> detailArrayList = new ArrayList<>();
        for (CartInfo cartInfo : cartInfoList) {
            OrderDetail orderDetail = new OrderDetail();

            orderDetail.setSkuId(cartInfo.getSkuId());
            orderDetail.setSkuName(cartInfo.getSkuName());
            orderDetail.setImgUrl(cartInfo.getImgUrl());
            orderDetail.setSkuNum(cartInfo.getSkuNum());
            orderDetail.setOrderPrice(cartInfo.getSkuPrice());

            // 添加到集合
            detailArrayList.add(orderDetail);
        }
        // 计算总金额
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setOrderDetailList(detailArrayList);
        orderInfo.sumTotalAmount();

        Map<String, Object> result = new HashMap<>();
        result.put("userAddressList", userAddressList);
        result.put("detailArrayList", detailArrayList);
        // 保存总金额
               result.put("totalNum", detailArrayList.size());
        result.put("totalAmount", orderInfo.getTotalAmount());

        return Result.ok(result);
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

// 计算总金额 需要所有的订单详情列表
OrderInfo orderInfo = new OrderInfo();
(detailArrayList);
();


 // 获取流水号
```java
    @Override
    public String getTradeNo(String userId) {
        // 定义key
        String tradeNoKey = "user:" + userId + ":tradeCode";
        // 定义一个流水号
        String tradeNo = ().toString().replace("-", "");
        ().set(tradeNoKey, tradeNo);
        return tradeNo;

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

2.4.5在web-all中添加控制器

参数 返回值

package com.atguigu.gmall.all.controller;


@Controller
public class OrderController {

    @Autowired
    private OrderFeignClient orderFeignClient;

    /**
     * 确认订单
          * @param model
     * @return
     */
    @GetMapping("")
    public String trade(Model model) {
        Result<Map<String, Object>> result = orderFeignClient.trade();

        model.addAllAttributes(result.getData());
        return "order/trade";
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

提交订单

submitOrder

   /**
     * 提交订单
     *
     * @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() + "库存不足!");
            }
            // 验证价格:
            BigDecimal skuPrice = productFeignClient.getSkuPrice(orderDetail.getSkuId());
            if (orderDetail.getOrderPrice().compareTo(skuPrice) != 0) {
                // 重新查询价格!
                cartFeignClient.loadCartCache(userId);
                return Result.fail().message(orderDetail.getSkuName() + "价格有变动!");
            }
        }


        // 验证通过,保存订单!
        Long orderId = orderService.saveOrderInfo(orderInfo);
        return Result.ok(orderId);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

三、下订单 保存订单

在这里插入图片描述

3.1 下单功能分析:

  1. 保存单据前要做记录:验库存,验价格 也就是提交订单
  2. 保存单据: orderInfo orderDetail。
  3. 保存以后把购物车中的商品删除。{不删!}
  4. 重定向到支付页面。
    最后进行交易

saveOrderInfo

//计算总价
没有做什么事就是把订单详情和订单表的信息进行更新

/**
     * 保存订单
     * @param orderInfo
     * @return
     */
    Long saveOrderInfo(OrderInfo orderInfo);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
实现类
package com.atguigu.gmall.order.service.impl;


@Service
public class OrderServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderService {

    @Autowired
    private OrderInfoMapper orderInfoMapper;

    @Autowired
    private OrderDetailMapper orderDetailMapper;

@Override
@Transactional
public Long saveOrderInfo(OrderInfo orderInfo) {
    orderInfo.sumTotalAmount();
    orderInfo.setOrderStatus(OrderStatus.UNPAID.name());
    String outTradeNo = "ATGUIGU" + System.currentTimeMillis() + "" + new Random().nextInt(1000);
    orderInfo.setOutTradeNo(outTradeNo);
    orderInfo.setCreateTime(new Date());
    // 定义为1天
    Calendar calendar = Calendar.getInstance();
    calendar.add(Calendar.DATE, 1);
    orderInfo.setExpireTime(calendar.getTime());

    orderInfo.setProcessStatus(ProcessStatus.UNPAID.name());
    // 获取订单明细
       List<OrderDetail> orderDetailList = orderInfo.getOrderDetailList();
    StringBuffer tradeBody = new StringBuffer();
    for (OrderDetail orderDetail : orderDetailList) {
        tradeBody.append(orderDetail.getSkuName()+" ");
    }
    if (tradeBody.toString().length()>100){
        orderInfo.setTradeBody(tradeBody.toString().substring(0,100));
    }else {
        orderInfo.setTradeBody(tradeBody.toString());
    }

    orderInfoMapper.insert(orderInfo);

    for (OrderDetail orderDetail : orderDetailList) {
        orderDetail.setOrderId(orderInfo.getId());
        orderDetailMapper.insert(orderDetail);  
    }
    return orderInfo.getId();
  }
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

3.4 submitOrder 编写控制器

提交订单之前 还要获取 userid

 /**
     * 提交订单
     *
     * @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() + "库存不足!");
            }
            // 验证价格:
            BigDecimal skuPrice = productFeignClient.getSkuPrice(orderDetail.getSkuId());
            if (orderDetail.getOrderPrice().compareTo(skuPrice) != 0) {
                // 重新查询价格!
                cartFeignClient.loadCartCache(userId);
                return Result.fail().message(orderDetail.getSkuName() + "价格有变动!");
            }
        }


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


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

checkStock

验证库存 向库存系统发送一个请求 参数为 skuId和对应的数量

// 验证库存:
boolean result = ((), ());

验证价格:

远程调用 根据skuId获取最新的价格 与当前订单 详情里的价格比较 如果不同
则根据用户Id 查询数据并放入购物车缓存!

           BigDecimal skuPrice = productFeignClient.getSkuPrice(orderDetail.getSkuId());
            if (orderDetail.getOrderPrice().compareTo(skuPrice) != 0) {
                // 重新查询价格!
                cartFeignClient.loadCartCache(userId);
                return Result.fail().message(orderDetail.getSkuName() + "价格有变动!");
            }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

最后才是保存订单

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

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

实现类
@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);
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

3.6 验库存与验证价格

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

3.6.2查询仓库数量,进行校验

@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);
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

3.6.4 优化下单

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

/**
 * 提交订单
 * @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) {
                // 重新查询价格!
                cartFeignClient.loadCartCache(userId);
                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);
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63