畅购商城(十一):订单
好好学习,天天向上
本文已收录至我的Github仓库DayDayUP:github.com/RobodLee/DayDayUP,欢迎Star,更多文章请前往:目录导航
- 畅购商城(一):环境搭建
- 畅购商城(二):分布式文件系统FastDFS
- 畅购商城(三):商品管理
- 畅购商城(四):Lua、OpenResty、Canal实现广告缓存与同步
- 畅购商城(五):Elasticsearch实现商品搜索
- 畅购商城(六):商品搜索
- 畅购商城(七):Thymeleaf实现静态页
- 畅购商城(八):微服务网关和JWT令牌
- 畅购商城(九):Spring Security Oauth2
- 畅购商城(十):购物车
- 畅购商城(十一):订单
收货地址
不论是在京东还是淘宝下单,都会去选择收货地址,那这些收货地址信息都是保存在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 』,第一时间阅读。