> 首先我们在这里严重的批评一些,在接口订单的接口中,直接传订单金额,而不是使用下单是已经计算好金额的人,这些接口岂不是使用0.01就能将全部的商品都买下来了?
我们回到订单设计这一个模块,首先我们在确认订单的时候就已经将价格计算完成了,那么我们肯定是想将计算结果给保留下来的,至于计算的过程,我们并不希望这个过程还要进行一遍的计算。
我们返回确认订单的接口,看到这样一行代码:
```java
@ApiOperation(value = "结算,生成订单信息", notes = "传入下单所需要的参数进行下单")
public ResponseEntity<ShopCartOrderMergerDto> confirm(@Valid @RequestBody OrderParam orderParam) {
orderService.putConfirmOrderCache(userId,shopCartOrderMergerDto);
}
```
这里每经过一次计算,就将整个订单通过`userId`进行了保存,而这个缓存的时间为30分钟,当用户使用
```java
@PostMapping("/submit")
@ApiOperation(value = "提交订单,返回支付流水号", notes = "根据传入的参数判断是否为购物车提交订单,同时对购物车进行删除,用户开始进行支付")
public ResponseEntity<OrderNumbersDto> submitOrders(@Valid @RequestBody SubmitOrderParam submitOrderParam) {
ShopCartOrderMergerDto mergerOrder = orderService.getConfirmOrderCache(userId);
if (mergerOrder == null) {
throw new YamiShopBindException("订单已过期,请重新下单");
}
// 省略中间一大段。。。
orderService.removeConfirmOrderCache(userId);
}
```
当无法获取缓存的时候告知用户订单过期,当订单进行提交完毕的时候,将之前的缓存给清除。
我们又回到提交订单中间这几行代码:
```java
List<Order> orders = orderService.submit(userId,mergerOrder);
```
这行代码也就是提交订单的核心代码
```java
eventPublisher.publishEvent(new SubmitOrderEvent(mergerOrder, orderList));
```
其中这里依旧是使用时间的方式,将订单进行提交,看下这个`SubmitOrderEvent`的默认监听事件。
```java
@Component("defaultSubmitOrderListener")
@AllArgsConstructor
public class SubmitOrderListener {
public void defaultSubmitOrderListener(SubmitOrderEvent event) {
// ...
}
}
```
这里有几段值得注意的地方:
- 这里是`UserAddrOrder` 并不是`UserAddr`:
```java
// 把订单地址保存到数据库
UserAddrOrder userAddrOrder = mapperFacade.map(mergerOrder.getUserAddr(), UserAddrOrder.class);
if (userAddrOrder == null) {
throw new YamiShopBindException("请填写收货地址");
}
userAddrOrder.setUserId(userId);
userAddrOrder.setCreateTime(now);
userAddrOrderService.save(userAddrOrder);
```
这里是将订单的收货地址进行了保存入库的操作,这里是绝对不能只保存用户的地址id在订单中的,要将地址入库,原因是如果用户在订单中设置了一个地址,如果用户在订单还没配送的时候,将自己的地址改了的话。如果仅采用关联的地址,就会出现问题。
- 为每个店铺生成一个订单
```java
// 每个店铺生成一个订单
for (ShopCartOrderDto shopCartOrderDto : shopCartOrders) {
}
```
这里为每个店铺创建一个订单,是为了,以后平台结算给商家时,每个商家的订单单独结算。用户确认收货时,也可以为每家店铺单独确认收货。
- 使用雪花算法生成订单id, 如果对雪花算法感兴趣的,可以去搜索下相关内容:
```java
String orderNumber = String.valueOf(snowflake.nextId());
```
我们不想单多台服务器生成的id冲突,也不想生成uuid这样的很奇怪的字符串id,更不想直接使用数据库主键这种东西时,雪花算法就出现咯。
- 当用户提交订单的时候,购物车里面勾选的商品,理所当然的要清空掉
```java
// 删除购物车的商品信息
if (!basketIds.isEmpty()) {
basketMapper.deleteShopCartItemsByBasketIds(userId, basketIds);
}
```
- 使用数据库的乐观锁,防止超卖:
```java
if (skuMapper.updateStocks(sku) == 0) {
skuService.removeSkuCacheBySkuId(key, sku.getProdId());
throw new YamiShopBindException("商品:[" + sku.getProdName() + "]库存不足");
}
```
```sql
update tz_sku set stocks = stocks - #{sku.stocks}, version = version + 1,update_time = NOW() where sku_id = #{sku.skuId} and #{sku.stocks} <= stocks
```
超卖一直是一件非常令人头疼的事情,如果对订单直接加悲观锁的话,那么下单的性能将会很差。商城最重要的就是下单啦,要是性能很差,那人家还下个鬼的单哟,所以我们采用数据库的乐观锁进行下单。
所谓乐观锁,就是在 where 条件下加上极限的条件,比如在这里就是更新的库存小于或等于商品的库存,在这种情况下可以对库存更新成功,则更新完成了,否则抛异常(真正的定义肯定不是这样的啦,你可以百度下 “乐观锁更新库存”)。注意这里在抛异常以前,应该将缓存也更新了,不然无法及时更新。
最后我们回到`controller`
```java
return ResponseEntity.ok(new OrderNumbersDto(orderNumbers.toString()));
```
这里面返回了多个订单项,这里就变成了并单支付咯,在多个店铺一起进行支付的时候需要进行并单支付的操作,一个店铺的时候,又要变成一个订单支付的操作,可是我们只希望有一个统一支付的接口进行调用,所以我们的支付接口要进行一点点的设计咯。
- 开发环境准备
- 基本开发手册
- 项目目录结构
- 权限管理
- 通用分页表格
- Swagger文档
- undertow容器
- 对xss攻击的防御
- 分布式锁
- 统一的系统日志
- 统一验证
- 统一异常处理
- 文件上传下载
- 一对多、多对多分页
- 认证与授权
- 从授权开始看源码
- 自己写个授权的方法-开源版
- 商城表设计
- 商品信息
- 商品分组
- 购物车
- 订单
- 地区管理
- 运费模板
- 接口设计
- 必读
- 购物车的设计
- 订单设计-确认订单
- 订单设计-提交订单
- 订单设计-支付
- 生产环境
- nginx安装与跨域配置
- 安装mysql
- 安装redis
- 传统方式部署项目
- docker
- 使用docker部署商城
- centos jdk安装
- docker centos 安装
- Docker Compose 安装与卸载
- docker 镜像的基本操作
- docker 容器的基本操作
- 通过yum安装maven
- 常见问题