|
@@ -659,7 +659,7 @@ public class OrderInfoController extends BaseController {
|
|
|
|
|
|
@Operation(summary = "订单结算")
|
|
|
@GetMapping("/trade")
|
|
|
- public AjaxResult orderTradeData() {
|
|
|
+ public AjaxResult trade() {
|
|
|
return success(orderInfoService.orderTradeData());
|
|
|
}
|
|
|
}
|
|
@@ -668,7 +668,7 @@ public class OrderInfoController extends BaseController {
|
|
|
#### 1.5.3 IOrderInfoService
|
|
|
|
|
|
```java
|
|
|
-TradeVo orderTradeData();
|
|
|
+TradeVo trade();
|
|
|
```
|
|
|
|
|
|
#### 1.5.4 OrderInfoServiceImpl
|
|
@@ -680,60 +680,64 @@ private RemoteCartService remoteCartService;
|
|
|
@Autowired
|
|
|
private RedisTemplate redisTemplate;
|
|
|
|
|
|
+/**
|
|
|
+ * 订单结算页相关参数封装
|
|
|
+ *
|
|
|
+ * @return
|
|
|
+ */
|
|
|
@Override
|
|
|
-public TradeVo orderTradeData() {
|
|
|
- // 获取当前登录用户的id
|
|
|
- Long userId = SecurityContextHolder.getUserId();
|
|
|
+public TradeVo trade() {
|
|
|
+ //0.创建订单结算VO对象
|
|
|
+ TradeVo tradeVo = new TradeVo();
|
|
|
|
|
|
- R<List<CartInfo>> cartInfoListResult = remoteCartService.getCartCheckedList(SecurityConstants.INNER);
|
|
|
- if (R.FAIL == cartInfoListResult.getCode()) {
|
|
|
- throw new ServiceException(cartInfoListResult.getMsg());
|
|
|
+ //1.远程调用购物车服务,获取选中的商品列表,封装订单明细列表
|
|
|
+ R<List<CartInfo>> r = cartService.getCartCheckedList(SecurityConstants.INNER);
|
|
|
+ //1.1 验证远程调用是否成功
|
|
|
+ if (R.FAIL == r.getCode()) {
|
|
|
+ throw new ServiceException("远程调用购物车服务失败,原因:" + r.getMsg());
|
|
|
}
|
|
|
- List<CartInfo> cartInfoList = cartInfoListResult.getData();
|
|
|
- if (CollectionUtils.isEmpty(cartInfoList)) {
|
|
|
- throw new ServiceException("购物车无选中商品");
|
|
|
+ //1.2 获取到选中购物车商品列表
|
|
|
+ List<CartInfo> cartInfoList = r.getData();
|
|
|
+ //1.3 将购物车商品列表转为订单明细列表
|
|
|
+ if (Collections.isEmpty(cartInfoList)) {
|
|
|
+ throw new ServiceException("没有需要结算商品");
|
|
|
}
|
|
|
+ //1.4 将购物车商品转为订单明细
|
|
|
+ List<OrderItem> orderItemList = cartInfoList
|
|
|
+ .stream()
|
|
|
+ .map(cartInfo -> {
|
|
|
+ OrderItem orderItem = new OrderItem();
|
|
|
+ BeanUtils.copyProperties(cartInfo, orderItem);
|
|
|
+ return orderItem;
|
|
|
+ }).collect(Collectors.toList());
|
|
|
+ tradeVo.setOrderItemList(orderItemList);
|
|
|
|
|
|
- //将集合泛型从购物车改为订单明细
|
|
|
- List<OrderItem> orderItemList = null;
|
|
|
+ //2.计算订单总金额
|
|
|
BigDecimal totalAmount = new BigDecimal(0);
|
|
|
- if (!CollectionUtils.isEmpty(cartInfoList)) {
|
|
|
- orderItemList = cartInfoList.stream().map(cartInfo -> {
|
|
|
- OrderItem orderItem = new OrderItem();
|
|
|
- BeanUtils.copyProperties(cartInfo, orderItem);
|
|
|
- orderItem.setSkuNum(cartInfo.getSkuNum());
|
|
|
- return orderItem;
|
|
|
- }).collect(Collectors.toList());
|
|
|
-
|
|
|
- //订单总金额
|
|
|
- for (OrderItem orderItem : orderItemList) {
|
|
|
- totalAmount = totalAmount.add(orderItem.getSkuPrice().multiply(new BigDecimal(orderItem.getSkuNum())));
|
|
|
- }
|
|
|
+ for (OrderItem orderItem : orderItemList) {
|
|
|
+ totalAmount = totalAmount.add(orderItem.getSkuPrice().multiply(new BigDecimal(orderItem.getSkuNum())));
|
|
|
}
|
|
|
+ tradeVo.setTotalAmount(totalAmount);
|
|
|
|
|
|
- //渲染订单确认页面-生成用户流水号(防止页面重复提交和页面等待超时)
|
|
|
- String tradeNo = this.generateTradeNo(userId);
|
|
|
|
|
|
- TradeVo tradeVo = new TradeVo();
|
|
|
- tradeVo.setTotalAmount(totalAmount);
|
|
|
- tradeVo.setOrderItemList(orderItemList);
|
|
|
+ //3.生成本次订单流水号-防止重复点击提交订单,存入Redis中 有效期5分钟
|
|
|
+ String tradeNo = this.generateTradeNo();
|
|
|
tradeVo.setTradeNo(tradeNo);
|
|
|
+
|
|
|
+ //4.封装结算对象VO响应
|
|
|
return tradeVo;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 渲染订单确认页面-生成用户流水号
|
|
|
+ * 生成当前用户结算订单流水号
|
|
|
*
|
|
|
- * @param userId
|
|
|
* @return
|
|
|
*/
|
|
|
-private String generateTradeNo(Long userId) {
|
|
|
- //1.构建流水号Key
|
|
|
- String userTradeKey = "user:tradeNo:" + userId;
|
|
|
- //2.构建流水号value
|
|
|
- String tradeNo = UUID.randomUUID().toString().replaceAll("-", "");
|
|
|
- //3.将流水号存入Redis 暂存5分钟
|
|
|
- redisTemplate.opsForValue().set(userTradeKey, tradeNo, 5, TimeUnit.MINUTES);
|
|
|
+@Override
|
|
|
+public String generateTradeNo() {
|
|
|
+ String tradeKey = "user:tradeNo:" + SecurityContextHolder.getUserId();
|
|
|
+ String tradeNo = UUID.randomUUID().toString();
|
|
|
+ redisTemplate.opsForValue().set(tradeKey, tradeNo, 5, TimeUnit.MINUTES);
|
|
|
return tradeNo;
|
|
|
}
|
|
|
```
|
|
@@ -776,9 +780,56 @@ post /order/orderInfo/submitOrder
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-### 2.2 获取商品最新价格接口
|
|
|
+### 2.2 批量查询商品实时价格
|
|
|
+
|
|
|
+获取最新商品sku价格与购物车价格比较,校验价格是否变化,价格变化就更新购物车价格
|
|
|
+
|
|
|
+#### 2.2.1 批量查询商品实时价格
|
|
|
+
|
|
|
+操作模块:`spzx-product`
|
|
|
+
|
|
|
+##### 1 ProductSkuController
|
|
|
+
|
|
|
+```java
|
|
|
+@Operation(summary = "批量获取商品sku最新价格信息")
|
|
|
+@InnerAuth
|
|
|
+@PostMapping(value = "/getSkuPriceList")
|
|
|
+public R<List<SkuPriceVo>> getSkuPriceList(@RequestBody List<Long> skuIdList)
|
|
|
+{
|
|
|
+ return R.ok(productSkuService.getSkuPriceList(skuIdList));
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+##### 2 IProductService
|
|
|
+
|
|
|
+```java
|
|
|
+List<SkuPriceVo> getSkuPriceList(List<Long> skuIdList);
|
|
|
+```
|
|
|
+
|
|
|
+##### 3 ProductServiceImpl
|
|
|
+
|
|
|
+```java
|
|
|
+@Override
|
|
|
+public List<SkuPriceVo> getSkuPriceList(List<Long> skuIdList) {
|
|
|
+ List<ProductSku> productSkuList = baseMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<ProductSku>()
|
|
|
+ .in(ProductSku::getId, skuIdList)
|
|
|
+ .select(ProductSku::getId, ProductSku::getSalePrice)
|
|
|
+ );
|
|
|
+ return productSkuList.stream().map(item -> {
|
|
|
+ SkuPriceVo skuPrice = new SkuPriceVo();
|
|
|
+ skuPrice.setSkuId(item.getId());
|
|
|
+ skuPrice.setSalePrice(item.getSalePrice());
|
|
|
+ return skuPrice;
|
|
|
+ }).collect(Collectors.toList());
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+
|
|
|
|
|
|
-#### 2.2.1 远程调用接口开发
|
|
|
+### 2.3 获取商品最新价格接口
|
|
|
+
|
|
|
+#### 2.3.1 远程调用接口开发
|
|
|
|
|
|
操作模块:spzx-product
|
|
|
|
|
@@ -794,7 +845,7 @@ public R<SkuPriceVo> getSkuPrice(@PathVariable("skuId") Long skuId)
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-#### 2.2.2 openFeign接口定义
|
|
|
+#### 2.3.2 openFeign接口定义
|
|
|
|
|
|
操作模块:spzx-api-product
|
|
|
|
|
@@ -818,35 +869,35 @@ public R<SkuPriceVo> getSkuPrice(Long skuId, String source) {
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-### 2.3 更新购物车最新价格
|
|
|
+### 2.4 更新购物车最新价格
|
|
|
|
|
|
操作模块:spzx-cart
|
|
|
|
|
|
-#### 2.3.1 远程调用接口开发
|
|
|
+#### 2.4.1 远程调用接口开发
|
|
|
|
|
|
##### 1 CartController
|
|
|
|
|
|
```java
|
|
|
@Operation(summary="更新用户购物车列表价格")
|
|
|
@InnerAuth
|
|
|
-@GetMapping("/updateCartPrice/{userId}")
|
|
|
-public R<Boolean> updateCartPrice(@PathVariable("userId") Long userId){
|
|
|
- return R.ok(cartService.updateCartPrice(userId));
|
|
|
+@GetMapping("/updateCartPrice")
|
|
|
+public R<Boolean> updateCartPrice(){
|
|
|
+ return R.ok(cartService.updateCartPrice());
|
|
|
}
|
|
|
```
|
|
|
|
|
|
##### 2 ICartService
|
|
|
|
|
|
```java
|
|
|
-Boolean updateCartPrice(Long userId);
|
|
|
+Boolean updateCartPrice();
|
|
|
```
|
|
|
|
|
|
##### 3 CartServiceImpl
|
|
|
|
|
|
```java
|
|
|
@Override
|
|
|
-public Boolean updateCartPrice(Long userId) {
|
|
|
- String cartKey = getCartKey(userId);
|
|
|
+public Boolean updateCartPrice() {
|
|
|
+ String cartKey = getCartKey(SecurityContextHolder.getUserId());
|
|
|
BoundHashOperations<String, String, CartInfo> hashOperations = redisTemplate.boundHashOps(cartKey);
|
|
|
List<CartInfo> cartCachInfoList = hashOperations.values();
|
|
|
if (!CollectionUtils.isEmpty(cartCachInfoList)) {
|
|
@@ -870,7 +921,7 @@ public Boolean updateCartPrice(Long userId) {
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-#### 2.3.2 openFeign接口定义
|
|
|
+#### 2.4.2 openFeign接口定义
|
|
|
|
|
|
操作模块:spzx-api-cart
|
|
|
|
|
@@ -879,7 +930,6 @@ public Boolean updateCartPrice(Long userId) {
|
|
|
```java
|
|
|
@GetMapping("/updateCartPrice/{userId}")
|
|
|
R<Boolean> updateCartPrice(
|
|
|
- @PathVariable("userId") Long userId,
|
|
|
@RequestHeader(SecurityConstants.FROM_SOURCE) String source
|
|
|
);
|
|
|
```
|
|
@@ -888,44 +938,44 @@ R<Boolean> updateCartPrice(
|
|
|
|
|
|
```java
|
|
|
@Override
|
|
|
-public R<Boolean> updateCartPrice(Long userId, String source) {
|
|
|
+public R<Boolean> updateCartPrice(String source) {
|
|
|
return R.fail("更新购物车价格失败:" + throwable.getMessage());
|
|
|
}
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
-### 2.4 删除购物车选中商品
|
|
|
+### 2.5 删除购物车选中商品
|
|
|
|
|
|
下单成功后,删除购物车选中的商品
|
|
|
|
|
|
操作模块:spzx-cart
|
|
|
|
|
|
-#### 2.4.1 远程调用接口开发
|
|
|
+#### 2.5.1 远程调用接口开发
|
|
|
|
|
|
##### 1 CartController
|
|
|
|
|
|
```java
|
|
|
@Operation(summary="删除用户购物车列表中选中商品列表")
|
|
|
@InnerAuth
|
|
|
-@GetMapping("/deleteCartCheckedList/{userId}")
|
|
|
-public R<Boolean> deleteCartCheckedList(@PathVariable("userId") Long userId){
|
|
|
- return R.ok(cartService.deleteCartCheckedList(userId));
|
|
|
+@GetMapping("/deleteCartCheckedList")
|
|
|
+public R<Boolean> deleteCartCheckedList(){
|
|
|
+ return R.ok(cartService.deleteCartCheckedList());
|
|
|
}
|
|
|
```
|
|
|
|
|
|
##### 2 ICartService
|
|
|
|
|
|
```java
|
|
|
- Boolean deleteCartCheckedList(Long userId);
|
|
|
+ Boolean deleteCartCheckedList();
|
|
|
```
|
|
|
|
|
|
##### 3 CartServiceImpl
|
|
|
|
|
|
```java
|
|
|
@Override
|
|
|
-public Boolean deleteCartCheckedList(Long userId) {
|
|
|
- String cartKey = getCartKey(userId);
|
|
|
+public Boolean deleteCartCheckedList() {
|
|
|
+ String cartKey = getCartKey(SecurityContextHolder.getUserId());
|
|
|
BoundHashOperations<String, String, CartInfo> hashOperations = redisTemplate.boundHashOps(cartKey);
|
|
|
List<CartInfo> cartCachInfoList = hashOperations.values();
|
|
|
if (!CollectionUtils.isEmpty(cartCachInfoList)) {
|
|
@@ -940,7 +990,7 @@ public Boolean deleteCartCheckedList(Long userId) {
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-#### 2.4.2 openFeign接口定义
|
|
|
+#### 2.5.2 openFeign接口定义
|
|
|
|
|
|
操作模块:spzx-api-cart
|
|
|
|
|
@@ -949,7 +999,6 @@ public Boolean deleteCartCheckedList(Long userId) {
|
|
|
```java
|
|
|
@GetMapping("/deleteCartCheckedList/{userId}")
|
|
|
R<Boolean> deleteCartCheckedList(
|
|
|
- @PathVariable("userId") Long userId,
|
|
|
@RequestHeader(SecurityConstants.FROM_SOURCE)String source
|
|
|
);
|
|
|
```
|
|
@@ -958,16 +1007,16 @@ R<Boolean> deleteCartCheckedList(
|
|
|
|
|
|
```java
|
|
|
@Override
|
|
|
-public R<Boolean> deleteCartCheckedList(Long userId, String source) {
|
|
|
+public R<Boolean> deleteCartCheckedList(String source) {
|
|
|
return R.fail("删除用户购物车选中数据失败:" + throwable.getMessage());
|
|
|
}
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
-### 2.5 获取用户地址信息(已定义)
|
|
|
+### 2.6 获取用户地址信息(已定义)
|
|
|
|
|
|
-#### 2.5.1 远程调用接口
|
|
|
+#### 2.6.1 远程调用接口
|
|
|
|
|
|
操作模块:spzx-user
|
|
|
|
|
@@ -986,7 +1035,7 @@ public R<UserAddress> getUserAddress(@PathVariable("id") Long id)
|
|
|
|
|
|
将spzx-user模块UserAddress实体类,移动到spzx-api-user模块
|
|
|
|
|
|
-#### 2.5.2 openFeign接口定义
|
|
|
+#### 2.6.2 openFeign接口定义
|
|
|
|
|
|
操作模块:spzx-api-user
|
|
|
|
|
@@ -1047,81 +1096,11 @@ resources/META-INF.spring/org.springframework.boot.autoconfigure.AutoConfigurati
|
|
|
com.spzx.user.api.factory.RemoteUserAddressFallbackFactory
|
|
|
```
|
|
|
|
|
|
-### 2.6 批量查询商品实时价格
|
|
|
-
|
|
|
-获取最新商品sku价格与购物车价格比较,校验价格是否变化,价格变化就更新购物车价格
|
|
|
-
|
|
|
-#### 2.6.1 批量查询商品实时价格
|
|
|
-
|
|
|
-操作模块:`spzx-product`
|
|
|
-
|
|
|
-##### 1 ProductSkuController
|
|
|
-
|
|
|
-```java
|
|
|
-@Operation(summary = "批量获取商品sku最新价格信息")
|
|
|
-@InnerAuth
|
|
|
-@PostMapping(value = "/getSkuPriceList")
|
|
|
-public R<List<SkuPriceVo>> getSkuPriceList(@RequestBody List<Long> skuIdList)
|
|
|
-{
|
|
|
- return R.ok(productSkuService.getSkuPriceList(skuIdList));
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-##### 2 IProductService
|
|
|
-
|
|
|
-```java
|
|
|
-List<SkuPriceVo> getSkuPriceList(List<Long> skuIdList);
|
|
|
-```
|
|
|
-
|
|
|
-##### 3 ProductServiceImpl
|
|
|
-
|
|
|
-```java
|
|
|
-@Override
|
|
|
-public List<SkuPriceVo> getSkuPriceList(List<Long> skuIdList) {
|
|
|
- List<ProductSku> productSkuList = baseMapper.selectList(
|
|
|
- new LambdaQueryWrapper<ProductSku>()
|
|
|
- .in(ProductSku::getId, skuIdList)
|
|
|
- .select(ProductSku::getId, ProductSku::getSalePrice)
|
|
|
- );
|
|
|
- return productSkuList.stream().map(item -> {
|
|
|
- SkuPriceVo skuPrice = new SkuPriceVo();
|
|
|
- skuPrice.setSkuId(item.getId());
|
|
|
- skuPrice.setSalePrice(item.getSalePrice());
|
|
|
- return skuPrice;
|
|
|
- }).collect(Collectors.toList());
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-#### 2.6.2 批量查询商品实时价格openFeign接口定义
|
|
|
-
|
|
|
-操作模块:`spzx-api-product`
|
|
|
-
|
|
|
-##### 1 RemoteProductService
|
|
|
-
|
|
|
-```java
|
|
|
-@PostMapping(value = "/productSku/getSkuPriceList")
|
|
|
-R<List<SkuPriceVo>> getSkuPriceList(
|
|
|
- @RequestBody List<Long> skuIdList,
|
|
|
- @RequestHeader(SecurityConstants.FROM_SOURCE) String source
|
|
|
-);
|
|
|
-```
|
|
|
-
|
|
|
-##### 2 RemoteProductFallbackFactory
|
|
|
-
|
|
|
-```java
|
|
|
-@Override
|
|
|
-public R<List<SkuPriceVo>> getSkuPriceList(List<Long> skuIdList, String source) {
|
|
|
- return R.fail("获取商品sku价格列表失败:" + throwable.getMessage());
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-### 2.6 后端业务接口
|
|
|
+### 2.7 后端业务接口
|
|
|
|
|
|
操作模块:spzx-order
|
|
|
|
|
|
-#### 2.6.1 OrderForm
|
|
|
+#### 2.7.1 OrderForm
|
|
|
|
|
|
```java
|
|
|
package com.spzx.order.form;
|
|
@@ -1152,7 +1131,7 @@ public class OrderForm {
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-#### 2.6.2 OrderInfoController
|
|
|
+#### 2.7.2 OrderInfoController
|
|
|
|
|
|
```java
|
|
|
@Operation(summary = "用户提交订单")
|
|
@@ -1162,13 +1141,13 @@ public AjaxResult submitOrder(@RequestBody OrderForm orderForm) {
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-#### 2.6.3 IOrderInfoService
|
|
|
+#### 2.7.3 IOrderInfoService
|
|
|
|
|
|
```java
|
|
|
Long submitOrder(OrderForm orderForm);
|
|
|
```
|
|
|
|
|
|
-#### 2.6.4 OrderInfoServiceImpl
|
|
|
+#### 2.7.4 OrderInfoServiceImpl
|
|
|
|
|
|
```java
|
|
|
@Autowired
|
|
@@ -1180,188 +1159,141 @@ private RemoteUserAddressService remoteUserAddressService;
|
|
|
@Autowired
|
|
|
private OrderLogMapper orderLogMapper;
|
|
|
|
|
|
-/**
|
|
|
- * 验证页面提交流水号是否有效
|
|
|
- *
|
|
|
- * @param userId
|
|
|
- * @param tradeNo
|
|
|
- * @return
|
|
|
- */
|
|
|
-private Boolean checkTradeNo(String userId, String tradeNo) {
|
|
|
- String userTradeKey = "user:tradeNo:" + userId;
|
|
|
- String redisTradeNo = (String) redisTemplate.opsForValue().get(userTradeKey);
|
|
|
- return tradeNo.equals(redisTradeNo);
|
|
|
-}
|
|
|
-
|
|
|
|
|
|
/**
|
|
|
- * 删除流水号
|
|
|
+ * 保存订单
|
|
|
*
|
|
|
- * @param userId
|
|
|
+ * @param orderForm
|
|
|
+ * @return
|
|
|
*/
|
|
|
-private void deleteTradeNo(String userId) {
|
|
|
- String userTradeKey = "user:tradeNo:" + userId;
|
|
|
- redisTemplate.delete(userTradeKey);
|
|
|
-}
|
|
|
-
|
|
|
-@Transactional(rollbackFor = Exception.class)
|
|
|
@Override
|
|
|
+@Transactional(rollbackFor = Exception.class)
|
|
|
public Long submitOrder(OrderForm orderForm) {
|
|
|
-
|
|
|
- //获取用户的userId
|
|
|
- Long userId = SecurityContextHolder.getUserId();
|
|
|
-
|
|
|
- //1 防止订单重复提交
|
|
|
- //判断订单号是否存在:
|
|
|
- // 如果存在则说明用户第一次提交此订单
|
|
|
- // 如果五分中内提交了第二次订单,则订单号不存在,则说明用户已经提交过该订单,不能重复提交
|
|
|
- // 如果五分中之后第一次提交,则订单号也不存在,说明操作超时
|
|
|
- /*if(!this.checkTradeNo(userId.toString(), orderForm.getTradeNo())){
|
|
|
- throw new ServiceException("请勿重复提交订单");
|
|
|
- }
|
|
|
- //删除redis中的订单号
|
|
|
- this.deleteTradeNo(userId.toString());*/
|
|
|
-
|
|
|
- //使用lua解决原子性的问题
|
|
|
- String userTradeKey = "user:tradeNo:" + userId;
|
|
|
- // 将键的值更为 processed 表示已处理 return 1 表示处理成功
|
|
|
- // return -1 键存在但值不匹配,表示重复提交
|
|
|
- // return 0 键不存在,订单过期
|
|
|
- String scriptText = """
|
|
|
- if redis.call('get', KEYS[1]) == ARGV[1] then
|
|
|
- redis.call('set', KEYS[1], 'processed')
|
|
|
- return 1
|
|
|
- else
|
|
|
- if redis.call('exists', KEYS[1]) == 1 then
|
|
|
- redis.call('del',KEYS[1])
|
|
|
- return -1
|
|
|
- else
|
|
|
- return 0
|
|
|
- end
|
|
|
- end
|
|
|
- """;
|
|
|
- DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
|
|
|
- redisScript.setScriptText(scriptText);
|
|
|
- redisScript.setResultType(Long.class);
|
|
|
- Long flag = (Long)redisTemplate.execute(redisScript, Arrays.asList(userTradeKey), orderForm.getTradeNo());
|
|
|
- if (flag == 0) {
|
|
|
- throw new ServiceException("操作超时,请退回重试");
|
|
|
- }
|
|
|
- if(flag == -1){
|
|
|
- throw new ServiceException("请勿重复提交订单");
|
|
|
+ //1.业务校验,验证流水号,防止订单重复提交或长时间停留订单结算页
|
|
|
+ String tradeKey = "user:tradeNo:" + SecurityContextHolder.getUserId();
|
|
|
+ //1.1 通过Lua脚本验证流水号 KEYS[1]:流水号Key ARGV[1]:用户提交流水号
|
|
|
+ String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
|
|
|
+ "then\n" +
|
|
|
+ " return redis.call(\"del\",KEYS[1])\n" +
|
|
|
+ "else\n" +
|
|
|
+ " return 0\n" +
|
|
|
+ "end\n";
|
|
|
+ RedisScript<Boolean> booleanRedisScript = new DefaultRedisScript<>(script, Boolean.class);
|
|
|
+ Boolean flag = (Boolean) redisTemplate.execute(booleanRedisScript, Arrays.asList(tradeKey), orderForm.getTradeNo());
|
|
|
+ if (!flag) {
|
|
|
+ throw new ServiceException("流水号校验失败,请重新提交");
|
|
|
}
|
|
|
|
|
|
- try {
|
|
|
- Thread.sleep(3000);
|
|
|
- } catch (InterruptedException e) {
|
|
|
- throw new RuntimeException(e);
|
|
|
- }
|
|
|
+ //2.验证订单中明细商品价格是否变更
|
|
|
+ //2.1 根据订单明细列表中skuID列表,远程调用"商品服务"得到实时商品价格列表
|
|
|
+ List<Long> skuIdList = orderForm.getOrderItemList().stream()
|
|
|
+ .map(OrderItem::getSkuId)
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
|
- //2 获取最新价格并判断价格是否改变
|
|
|
- //批量获取最新价格
|
|
|
- List<OrderItem> orderItemList = orderForm.getOrderItemList();
|
|
|
- List<Long> skuIdList = orderItemList.stream().map(orderItem -> orderItem.getSkuId()).collect(Collectors.toList());
|
|
|
- R<List<SkuPriceVo>> skuPriceListResult =remoteProductService.getSkuPriceList(skuIdList, SecurityConstants.INNER);
|
|
|
- if(R.FAIL == skuPriceListResult.getCode()){
|
|
|
- throw new ServiceException(skuPriceListResult.getMsg());
|
|
|
+ R<List<SkuPriceVo>> r = remoteProductService.getSkuPriceList(skuIdList, SecurityConstants.INNER);
|
|
|
+ if (R.FAIL == r.getCode()) {
|
|
|
+ throw new ServiceException("远程调用商品服务失败,原因:" + r.getMsg());
|
|
|
}
|
|
|
- List<SkuPriceVo> skuPriceList = skuPriceListResult.getData();
|
|
|
- //判断价格是否发生变化
|
|
|
- String priceCheckResult = "";
|
|
|
- Map<Long, BigDecimal> skuIdSalePriceMap = skuPriceList.stream().collect(Collectors.toMap(SkuPriceVo::getSkuId, SkuPriceVo::getSalePrice));
|
|
|
- for (OrderItem orderItem : orderItemList) {
|
|
|
- if(orderItem.getSkuPrice().compareTo(skuIdSalePriceMap.get(orderItem.getSkuId())) != 0){
|
|
|
- //价格发生了变化
|
|
|
- priceCheckResult += orderItem.getSkuName() + "\n";
|
|
|
+ List<SkuPriceVo> skuPriceVoList = r.getData();
|
|
|
+ Map<Long, BigDecimal> skuPriceMap =
|
|
|
+ skuPriceVoList.stream().collect(Collectors.toMap(SkuPriceVo::getSkuId, SkuPriceVo::getSalePrice));
|
|
|
+
|
|
|
+ //2.2 判断商品价格是否有变更,如果有变更
|
|
|
+ String priceErrorMsg = "";
|
|
|
+ for (OrderItem orderItem : orderForm.getOrderItemList()) {
|
|
|
+ if (orderItem.getSkuPrice().compareTo(skuPriceMap.get(orderItem.getSkuId())) != 0) {
|
|
|
+ priceErrorMsg += orderItem.getSkuName() + "\n";
|
|
|
}
|
|
|
}
|
|
|
- if(!StringUtils.isEmpty(priceCheckResult)){
|
|
|
-
|
|
|
- if(!orderForm.getIsBuy()){
|
|
|
- //更新购物车中的价格
|
|
|
- remoteCartService.updateCartPrice(userId, SecurityConstants.INNER);
|
|
|
+ if (StringUtils.isNotBlank(priceErrorMsg)) {
|
|
|
+ //2.2.1 如果是通过购物车车途径提交订单,则远程调用"购物车服务"更新购物车中商品价格
|
|
|
+ if (!orderForm.getIsBuy()) {
|
|
|
+ remoteCartService.updateCartPrice(SecurityConstants.INNER);
|
|
|
}
|
|
|
- throw new ServiceException(priceCheckResult + "以上商品价格发生变化,请确认");
|
|
|
- }
|
|
|
|
|
|
- //3 校验库存并锁定库存 TODO
|
|
|
+ //2.2.2 则提示价格有变更,重新结算,引导用户购物车页面
|
|
|
+ throw new ServiceException(priceErrorMsg + "以上商品价格有变动");
|
|
|
+ }
|
|
|
|
|
|
- //4 下单
|
|
|
+ //4.如果商品价格未变更:保存订单及明细,日志
|
|
|
Long orderId = this.saveOrder(orderForm);
|
|
|
|
|
|
- //5 删除购物车选中商品
|
|
|
- if(!orderForm.getIsBuy()){
|
|
|
- remoteCartService.deleteCartCheckedList(userId, SecurityConstants.INNER);
|
|
|
+ //5.TODO 锁定商品库存,基于RabbitMQ异步锁定库存
|
|
|
+
|
|
|
+ //6.保存订单成功后, 如果是通过购物车车途径提交订单,清理用户购物车
|
|
|
+ if (!orderForm.getIsBuy()) {
|
|
|
+ remoteCartService.deleteCartCheckedList(SecurityConstants.INNER);
|
|
|
}
|
|
|
+ //7.响应订单ID,对接支付页面
|
|
|
+ //TODO :RabbitMQ(死信、延迟插件)发送延迟消息 作用:自动将超时未支付订单关闭(延迟关单)
|
|
|
return orderId;
|
|
|
}
|
|
|
|
|
|
-private Long saveOrder(OrderForm orderForm) {
|
|
|
|
|
|
+
|
|
|
+/**
|
|
|
+ * 保存订单相关信息
|
|
|
+ *
|
|
|
+ * @param orderForm
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+@Override
|
|
|
+public Long saveOrder(OrderForm orderForm) {
|
|
|
+ //1.保存订单信息
|
|
|
Long userId = SecurityContextHolder.getUserId();
|
|
|
String userName = SecurityContextHolder.getUserName();
|
|
|
-
|
|
|
- // 保存订单记录:order_info
|
|
|
OrderInfo orderInfo = new OrderInfo();
|
|
|
+ //1.1 用户信息
|
|
|
orderInfo.setUserId(userId);
|
|
|
- orderInfo.setOrderNo(orderForm.getTradeNo());//订单号
|
|
|
orderInfo.setNickName(userName);
|
|
|
+ //1.2 生成订单编号 形式:日期+全局唯一ID(雪花算法)
|
|
|
+ String orderNo = DateUtils.dateTime() + IdWorker.getIdStr();
|
|
|
+ orderInfo.setOrderNo(orderNo);
|
|
|
+ //1.3 遍历订单明细列表计算订单总金额
|
|
|
+ BigDecimal totalAmount = orderForm.getOrderItemList().stream()
|
|
|
+ .map(OrderItem::getSkuPrice)
|
|
|
+ //从0开始累加结合中每个元素值
|
|
|
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
+ orderInfo.setTotalAmount(totalAmount);
|
|
|
+ orderInfo.setOriginalTotalAmount(totalAmount);
|
|
|
+ //1.4 订单状态:待付款
|
|
|
+ orderInfo.setOrderStatus(0);
|
|
|
+ orderInfo.setCreateBy(userName);
|
|
|
orderInfo.setRemark(orderForm.getRemark());
|
|
|
- orderInfo.setFeightFee(orderForm.getFeightFee());
|
|
|
-
|
|
|
- //远程调用获取用户地址信息
|
|
|
- R<UserAddress> userAddressResult = remoteUserAddressService.getUserAddress(orderForm.getUserAddressId(), SecurityConstants.INNER);
|
|
|
- if(R.FAIL == userAddressResult.getCode()){
|
|
|
- throw new ServiceException(userAddressResult.getMsg());
|
|
|
+ //1.5 封装订单收件人信息 远程调用"用户服务"得到收件人信息
|
|
|
+ R<UserAddress> r = remoteUserAddressService.getUserAddress(orderForm.getUserAddressId(), SecurityConstants.INNER);
|
|
|
+ if (R.FAIL == r.getCode()) {
|
|
|
+ throw new ServiceException("远程调用用户服务失败,原因:" + r.getMsg());
|
|
|
}
|
|
|
- //获取用户地址信息
|
|
|
- UserAddress userAddress = userAddressResult.getData();
|
|
|
- //保存用户地址信息
|
|
|
+ UserAddress userAddress = r.getData();
|
|
|
orderInfo.setReceiverName(userAddress.getName());
|
|
|
orderInfo.setReceiverPhone(userAddress.getPhone());
|
|
|
orderInfo.setReceiverTagName(userAddress.getTagName());
|
|
|
orderInfo.setReceiverProvince(userAddress.getProvinceCode());
|
|
|
orderInfo.setReceiverCity(userAddress.getCityCode());
|
|
|
orderInfo.setReceiverDistrict(userAddress.getDistrictCode());
|
|
|
- orderInfo.setReceiverAddress(userAddress.getFullAddress());
|
|
|
+ orderInfo.setReceiverAddress(userAddress.getAddress());
|
|
|
+ save(orderInfo);
|
|
|
+ Long orderId = orderInfo.getId();
|
|
|
|
|
|
- //实时获取总价格
|
|
|
- //计算orderItem中商品的总价格
|
|
|
+ //2.保存订单明细信息
|
|
|
List<OrderItem> orderItemList = orderForm.getOrderItemList();
|
|
|
- BigDecimal totalAmount = orderItemList
|
|
|
- .stream()
|
|
|
- .map(orderItem -> orderItem.getSkuPrice().multiply(new BigDecimal(orderItem.getSkuNum())))
|
|
|
- .reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
- //设置商品总价格
|
|
|
- orderInfo.setTotalAmount(totalAmount);
|
|
|
- orderInfo.setCouponAmount(new BigDecimal(0));//优惠券价格
|
|
|
- orderInfo.setOriginalTotalAmount(totalAmount);//原价
|
|
|
- //订单状态
|
|
|
- orderInfo.setOrderStatus(0);//待付款
|
|
|
-
|
|
|
- orderInfo.setCreateBy(userName);
|
|
|
- orderInfo.setUpdateBy(userName);
|
|
|
- baseMapper.insert(orderInfo);
|
|
|
-
|
|
|
- // 保存订单项记录:order_item
|
|
|
- for (OrderItem orderItem : orderItemList) {
|
|
|
- orderItem.setOrderId(orderInfo.getId());
|
|
|
- orderItem.setCreateBy(userName);
|
|
|
- orderItem.setUpdateBy(userName);
|
|
|
- orderItemMapper.insert(orderItem);
|
|
|
+ if (!CollectionUtils.isEmpty(orderItemList)) {
|
|
|
+ for (OrderItem orderItem : orderItemList) {
|
|
|
+ orderItem.setOrderId(orderId);
|
|
|
+ orderItem.setCreateBy(userName);
|
|
|
+ orderItemMapper.insert(orderItem);
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
- // 保存订单日志记录:order_log
|
|
|
+ //3.保存订单操作日志
|
|
|
OrderLog orderLog = new OrderLog();
|
|
|
- orderLog.setOrderId(orderInfo.getId());
|
|
|
+ orderLog.setOrderId(orderId);
|
|
|
+ orderLog.setOperateUser(userId.toString());
|
|
|
orderLog.setProcessStatus(0);
|
|
|
- orderLog.setOperateUser(userName);
|
|
|
- orderLog.setNote("提交订单");
|
|
|
+ orderLog.setNote(orderForm.getRemark());
|
|
|
orderLog.setCreateBy(userName);
|
|
|
- orderLog.setUpdateBy(userName);
|
|
|
orderLogMapper.insert(orderLog);
|
|
|
-
|
|
|
- return orderInfo.getId();
|
|
|
+ return orderId;
|
|
|
}
|
|
|
```
|
|
|
|
|
@@ -1427,45 +1359,39 @@ TradeVo buy(Long skuId);
|
|
|
#### 3.2.3、OrderInfoServiceImpl
|
|
|
|
|
|
```java
|
|
|
+/**
|
|
|
+ * 订单结算页面渲染
|
|
|
+ *
|
|
|
+ * @param skuId
|
|
|
+ * @return
|
|
|
+ */
|
|
|
@Override
|
|
|
public TradeVo buy(Long skuId) {
|
|
|
-
|
|
|
- //获取商品SKU信息
|
|
|
- R<ProductSku> productSkuResult = remoteProductService.getProductSku(skuId, SecurityConstants.INNER);
|
|
|
- if(R.FAIL == productSkuResult.getCode()){
|
|
|
- throw new ServiceException(productSkuResult.getMsg());
|
|
|
- }
|
|
|
- //获取实时价格
|
|
|
- R<SkuPriceVo> skuPriceResult = remoteProductService.getSkuPrice(skuId, SecurityConstants.INNER);
|
|
|
- if(R.FAIL == skuPriceResult.getCode()){
|
|
|
- throw new ServiceException(skuPriceResult.getMsg());
|
|
|
+ //1.创建订单确认VO对象
|
|
|
+ TradeVo tradeVo = new TradeVo();
|
|
|
+ //2.封装订单明细
|
|
|
+ //2.1 远程调用"商品服务"查询商品信息
|
|
|
+ R<ProductSku> r = remoteProductService.getProductSku(skuId, SecurityConstants.INNER);
|
|
|
+ if (R.FAIL == r.getCode()) {
|
|
|
+ throw new ServiceException("远程调用商品服务失败,原因:" + r.getMsg());
|
|
|
}
|
|
|
-
|
|
|
- ProductSku productSku = productSkuResult.getData();
|
|
|
- SkuPriceVo skuPrice = skuPriceResult.getData();
|
|
|
-
|
|
|
- List<OrderItem> orderItemList = new ArrayList<>();
|
|
|
+ ProductSku productSku = r.getData();
|
|
|
+ //2.2 封装订单明细列表
|
|
|
OrderItem orderItem = new OrderItem();
|
|
|
orderItem.setSkuId(skuId);
|
|
|
orderItem.setSkuName(productSku.getSkuName());
|
|
|
- orderItem.setSkuNum(1);
|
|
|
- orderItem.setSkuPrice(skuPrice.getSalePrice());//填充最新价格
|
|
|
orderItem.setThumbImg(productSku.getThumbImg());
|
|
|
-
|
|
|
- orderItemList.add(orderItem);
|
|
|
-
|
|
|
-
|
|
|
- //订单总金额
|
|
|
- BigDecimal totalAmount = skuPrice.getSalePrice();//填充最新价格
|
|
|
-
|
|
|
- //渲染订单确认页面-生成用户流水号
|
|
|
- String tradeNo = this.generateTradeNo(SecurityUtils.getUserId());
|
|
|
-
|
|
|
- TradeVo tradeVo = new TradeVo();
|
|
|
- tradeVo.setTotalAmount(totalAmount);
|
|
|
+ orderItem.setSkuPrice(productSku.getSalePrice());
|
|
|
+ orderItem.setSkuNum(1);
|
|
|
+ List<OrderItem> orderItemList = Arrays.asList(orderItem);
|
|
|
tradeVo.setOrderItemList(orderItemList);
|
|
|
+ //3.封装订单总金额
|
|
|
+ tradeVo.setTotalAmount(orderItem.getSkuPrice());
|
|
|
+ //4.生成流水号
|
|
|
+ String tradeNo = this.generateTradeNo();
|
|
|
tradeVo.setTradeNo(tradeNo);
|
|
|
- tradeVo.setIsBuy(true); //立即购买
|
|
|
+ //5.设置立即购买为:true
|
|
|
+ tradeVo.setIsBuy(true);
|
|
|
return tradeVo;
|
|
|
}
|
|
|
```
|