订单业务在整个电商平台中处于核心位置,也是比较复杂的一块业务。是把“物”变为“钱”的一个中转站。
整个订单模块一共分四部分组成:
- 结算
- 下单
- 对接支付服务
- 对接库存管理系统
入口:购物车点击计算按钮 ,结算必须要登录!
分析页面需要的数据:
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 下单功能分析:
- 保存单据前要做记录:验库存,验价格 也就是提交订单
- 保存单据: orderInfo orderDetail。
- 保存以后把购物车中的商品删除。{不删!}
- 重定向到支付页面。
最后进行交易
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