畅购商城(十一):订单

好好学习,天天向上

本文已收录至我的Github仓库DayDayUP:github.com/RobodLee/DayDayUP,欢迎Star,更多文章请前往:目录导航

收货地址

不论是在京东还是淘宝下单,都会去选择收货地址,那这些收货地址信息都是保存在MySQL中的,所以第一步要实现查询收货地址的功能。思路就是通过用户名去查询出收货地址,但并不是前端将用户名传到服务器,而是直接解析Token拿到用户名,然后再去查询数据库,这样做的好处就是安全。实现起来很简单,直接上代码。解析Token用的还是之前写的工具类。

//	AddressController
@GetMapping("/user/list")
public Result<List<Address>> list() {
    String username = tokenDecodeUtil.getUserInfo().get("username");
    List<Address> addresses = addressService.list(username);
    return new Result<>(true,StatusCode.OK,"查询成功",addresses);
}
------------------------------------------------------------------------
//	AddressServiceImpl
@Override
public List<Address> list(String username) {
    return addressMapper.list(username);
}
-------------------------------------------------------------------------
//	AddressMapper
@Select("select * from tb_address where username = #{username}")
List<Address> list(String username);

下单

下单并不是前端直接将订单的信息传给服务器,服务器直接创建订单这么简单。这里面有两个需要注意的点:

  • 价格校验

    比如现在有个商品做活动,原价99元,现在限时88元。我在点击提交订单按钮之前价格就已经恢复到99元的,但是页面上的内容没有刷新,还是88元。所以订单提到到服务器的时候,后端这边应该从数据库里面将价格再查询一遍,做个校验,以数据库查询出来的为准,这样可以防止价格变动导致的损失。

  • 库存检查

    检查库存是为了防止超卖,比如我进入到商品详情页的时候显示还剩5件,就在提交订单之前,该商品卖完了,但是由于界面未刷新导致显示还有货。所以订单提交到服务器的时候。后端先从数据库里面查询一下时候还有货,如果没有货的话订单就创建失败,反之成功。

所以说,并不是所有的东西都是从前端传过来的,具体的可以看一下和订单相关的两张表。

和订单有关的有两张表,订单是点击下单后生成的记录,比如我一次性下单10件商品,就是一个订单,但是生成了10个订单明细。其中,支付类型收货人收货人手机收货人地址是前端传过来的,其它的信息则是由后端去完善的。

在介绍完了这些内容之后,就可以去编写代码了。

@PostMapping
public Result add(@RequestBody Order order){
    String username = tokenDecodeUtil.getUserInfo().get("username");
    order.setUsername(username);
    orderService.add(order);
    return new Result(true,StatusCode.OK,"添加成功");
}
---------------------------------------------------------------------------
@Override
public synchronized void add(Order order) {
    order.setId(String.valueOf(idWorker.nextId()));
    BoundHashOperations boundHashOperations = redisTemplate.boundHashOps("Cart_" + order.getUsername());
    int totalNum=0,totalMoney=0;    //总数量,总金额
    LocalDateTime localDateTime = LocalDateTime.now();
    List<OrderItem> orderItems = boundHashOperations.values();   //从购物车中获取订单明细
    if (orderItems == null || orderItems.size()==0) {
        throw new RuntimeException("购物车数据异常,下单失败");
    }
    List<Sku> skuList = skuFeign.findBySkuIds(order.getSkuIds()).getData(); //数据库中对应的sku集合
    //如果数据库中查询出来的sku集合数量与前端传过来的sku数量不一致,说明数据有误,下单失败
    if (skuList.size() != order.getSkuIds().size()){
        throw new RuntimeException("sku数据库数据异常,下单失败");
    }
    Map<Long,Sku> skuMap = skuList.stream().collect(Collectors.toMap(Sku::getId,a -> a));
    //遍历购物车中的数据,判断是否是选中的,将选中的订单明细数据补充完整
    for (OrderItem orderItem : orderItems) {
        if (order.getSkuIds().contains(orderItem.getSkuId())) {     //判断当前遍历到的orderItem是否是选中的
            orderItem.setId(String.valueOf(idWorker.nextId()));
            orderItem.setOrderId(order.getId());
            orderItem.setIsReturn("0");
            Sku sku = skuMap.get(orderItem.getSkuId()); //数据库中的sku
            if (orderItem.getNum() <= sku.getNum()) {   //判断库存是否充足,不足则报异常订单提交失败
                totalNum += orderItem.getNum();
            } else {
                throw new RuntimeException("库存不足,下单失败");
            }
            totalMoney += sku.getPrice();
        }
    }
    //减库存,删购物车
    for (OrderItem orderItem : orderItems) {
        if (order.getSkuIds().contains(orderItem.getSkuId())) {
            Sku sku = skuMap.get(orderItem.getSkuId()); //数据库中的sku
            sku.setNum(sku.getNum() - orderItem.getNum());	//减库存
            boundHashOperations.delete(orderItem.getSkuId());	//删购物车
            orderItemMapper.insertSelective(orderItem); //添加到订单明细表
        }
    }
    skuFeign.updateMap(skuMap); //将sku信息提交到数据库中的sku表
    
    order.setCreateTime(localDateTime);
    order.setUpdateTime(localDateTime);
    order.setTotalNum(totalNum);
    order.setTotalMoney(totalMoney);
    order.setSourceType("1");   //1.web
    order.setOrderStatus("0");
    order.setPayStatus("0");
    order.setIsDelete("0");
    orderMapper.insertSelective(order); //添加到订单表
}

这段代码有点长,首先在Controller层,通过解析Token,拿到了用户名,然后到了Service层。order对象里面有个字段

private List<Long> skuIds;  //选中的sku的id

这个是在页面选中的商品的id的集合。因为下单的时候,可以选择购物车中的部分商品,并不是所有的商品,用这个就可以判断哪些是选中的。

在Service层中,先是通过skuIds去调用Feign拿到对应的商品集合数据,因为需要进行库存判断,减库存以及价格查询,所以才去拿到这些数据,为了方便使用,将其转成Map集合。然后查询出购物车中的所有数据,因为我们需要将下单的商品从购物车中移除。

然后就开始第一遍遍历购物车中的商品,目的是进行库存判断防止超卖,计算总数量,计算总金额。如果下单的数量大于库存,就下单失败。

如果第一遍遍历的时候没有发生超卖的现象,就可以进行第二遍遍历购物车,目的是减库存,删除购物车中已下单的商品,将订单明细添加到订单明细表中。等遍历完了之后,就将改过库存的sku信息提交到数据库中。使用两次循环而不是一次的原因是只要有一件商品库存不足,那么此次下单就应该不成功,购物车中的数据不做修改,一次循环满足不了需求。

最后将order信息补充完整后添加到订单表中。至此,订单就算创建完成了。

在这段代码中,对几个可能会发生异常的情况做了处理。

  • 如果购物车中的数据为0,说明可能是前端传错了令牌,解析出来的用户名下没有商品信息,此时下单失败。
  • 拿着skuId集合去数据库中查询对应的sku集合。如果数据量不一致,说明可能前端传过来的某个skuId有误或者数据库中的数据有误,那么下单失败。
  • 库存不足,下单失败。
  • 为了确保线程安全,加了synchronized关键字。

好了,下订单的功能就完成了。

还有一个用户积分的功能,这个就比较简单了,就是通过Feign去调用UserController中的方法,然后根据用户名修改积分数据,没什么好说的,代码就不贴了。

总结

这篇文章到这里就结束了,内容比较少,就两个,一个是根据用户名去查询收获地址,还有一个是下单的功能,下单功能不算复杂,就是有些可能会出现问题的几个点需要处理。没有支付的功能怎么能叫下单呢,下篇文章就去实现一下支付的功能。让我们下期再见!

码字不易,可以的话,给我来个点赞,收藏,关注

如果你喜欢我的文章,欢迎关注微信公众号 R o b o d,第一时间阅读。

代码:https://github.com/RobodLee/changgou

posted @ 2020-08-31 13:54  Robod丶  阅读(430)  评论(0编辑  收藏  举报