it_lv преди 2 седмици
родител
ревизия
bde530265d
променени са 1 файла, в които са добавени 223 реда и са изтрити 185 реда
  1. 223 185
      第7章 订单.md

+ 223 - 185
第7章 订单.md

@@ -1532,28 +1532,27 @@ public Result savePaidRecord(UserPaidRecordVo userPaidRecordVo) {
 
 - 抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
 
-- 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
+- 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供**具体**的算法实现或行为。
 
 - 环境(Context)类:用来操作策略的上下文环境,屏蔽高层模块(客户端)对策略、算法的直接访问,封装可能存在的变化。
 
 **策略接口**
 
 ```java
-package com.atguigu.tingshu.user.strategy;
+package com.atguigu.tingshu.user.pattern;
 
 import com.atguigu.tingshu.vo.user.UserPaidRecordVo;
 
 /**
- * 处理不同类型商品购买记录的策略接口
+ * 抽象”发货“策略接口
  */
-public interface PaidRecordStrategy {
-
+public interface DeliveryStrategy {
 
     /**
-     * 抽象出公用处理不同购买项目类型接口方法
+     * 虚拟物品"发货"抽象方法
      * @param userPaidRecordVo
      */
-    public void handlerPaidRecord(UserPaidRecordVo userPaidRecordVo);
+    void delivery(UserPaidRecordVo userPaidRecordVo);
 
 }
 ```
@@ -1561,17 +1560,18 @@ public interface PaidRecordStrategy {
 **VIP购买项处理策略实现类**:其中Bean对象ID要跟前端提交项目类型一致
 
 ```java
-package com.atguigu.tingshu.user.strategy.impl;
+package com.atguigu.tingshu.user.pattern.impl;
 
+import cn.hutool.core.date.DateTime;
 import cn.hutool.core.date.DateUtil;
+import com.atguigu.tingshu.common.constant.SystemConstant;
 import com.atguigu.tingshu.model.user.UserInfo;
 import com.atguigu.tingshu.model.user.UserVipService;
 import com.atguigu.tingshu.model.user.VipServiceConfig;
 import com.atguigu.tingshu.user.mapper.UserInfoMapper;
 import com.atguigu.tingshu.user.mapper.UserVipServiceMapper;
 import com.atguigu.tingshu.user.mapper.VipServiceConfigMapper;
-import com.atguigu.tingshu.user.strategy.PaidRecordStrategy;
-import com.atguigu.tingshu.vo.user.UserInfoVo;
+import com.atguigu.tingshu.user.pattern.DeliveryStrategy;
 import com.atguigu.tingshu.vo.user.UserPaidRecordVo;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import lombok.extern.slf4j.Slf4j;
@@ -1582,11 +1582,11 @@ import java.util.Date;
 
 /**
  * @author: atguigu
- * @create: 2024-08-23 10:32
+ * @create: 2025-06-16 09:36
  */
 @Slf4j
-@Component("1003")
-public class VIPPaidRecordStrategy implements PaidRecordStrategy {
+@Component(SystemConstant.ORDER_ITEM_TYPE_VIP)
+public class VIPDelivery implements DeliveryStrategy {
 
     @Autowired
     private UserVipServiceMapper userVipServiceMapper;
@@ -1598,47 +1598,50 @@ public class VIPPaidRecordStrategy implements PaidRecordStrategy {
     private UserInfoMapper userInfoMapper;
 
     /**
-     * 处理购买商品类型为会员-发货逻辑
+     * 虚拟物品-普通会员-发货行为实现
+     *
      * @param userPaidRecordVo
      */
     @Override
-    public void handlerPaidRecord(UserPaidRecordVo userPaidRecordVo) {
-        log.info("[用户服务]处理购买商品类型为会员发货逻辑");
-        //3.处理购买项目类型-VIP会员
-        //3.1 根据订单编号查询是否处理过VIP购买记录
-        LambdaQueryWrapper<UserVipService> userVipServiceLambdaQueryWrapper = new LambdaQueryWrapper<>();
-        userVipServiceLambdaQueryWrapper.eq(UserVipService::getOrderNo, userPaidRecordVo.getOrderNo());
-        Long count = userVipServiceMapper.selectCount(userVipServiceLambdaQueryWrapper);
+    public void delivery(UserPaidRecordVo userPaidRecordVo) {
+        //3. 处理虚拟物品"会员"发货逻辑
+        //3.1 根据订单编号查询已购会员记录,如果存在,则返回
+        Long count = userVipServiceMapper.selectCount(
+                new LambdaQueryWrapper<UserVipService>()
+                        .eq(UserVipService::getOrderNo, userPaidRecordVo.getOrderNo())
+        );
         if (count > 0) {
             return;
         }
-        //3.2 保存本次会员购买记录
-        UserVipService userVipService = new UserVipService();
-        //3.2.1 获取当前用户VIP身份
+        //3.2 判断当前用户是否为会员
         Boolean isVIP = false;
         UserInfo userInfo = userInfoMapper.selectById(userPaidRecordVo.getUserId());
-        if (userInfo.getIsVip().intValue() == 1 && userInfo.getVipExpireTime().after(new Date())) {
+        Date now = new Date();
+        if (userInfo.getIsVip().intValue() == 1 && userInfo.getVipExpireTime().after(now)) {
             isVIP = true;
         }
-        //3.2.2 计算本次会员开始及过期时间
-        //3.2.3 获取VIP套餐信息
+        //3.3 新增会员购买记录
+        UserVipService userVipService = new UserVipService();
+        userVipService.setOrderNo(userPaidRecordVo.getOrderNo());
+        userVipService.setUserId(userPaidRecordVo.getUserId());
+        //3.3.1 获取会员套餐信息得到服务月数
         VipServiceConfig vipServiceConfig = vipServiceConfigMapper.selectById(userPaidRecordVo.getItemIdList().get(0));
         Integer serviceMonth = vipServiceConfig.getServiceMonth();
-
-        Date startTime = new Date();
+        //3.3.2 计算本次会员购买记录生效时间
+        //3.3.3 计算本次会员购买记录失效时间
         if (!isVIP) {
-            //普通用户
-            userVipService.setStartTime(startTime);
-            userVipService.setExpireTime(DateUtil.offsetMonth(startTime, serviceMonth));
+            //普通用户,生效时间为当前时间 失效时间=当前时间+购买会员套餐月数
+            userVipService.setStartTime(now);
+            DateTime expireTime = DateUtil.offsetMonth(now, serviceMonth);
+            userVipService.setExpireTime(expireTime);
         } else {
-            startTime = DateUtil.offsetDay(userInfo.getVipExpireTime(), 1);
-            userVipService.setStartTime(startTime);
-            userVipService.setExpireTime(DateUtil.offsetMonth(startTime, serviceMonth));
+            //会员用户,生效时间为=会员的到期时间+1 失效时间=会员的到期时间+购买会员套餐月数
+            Date vipExpireTime = userInfo.getVipExpireTime();  //6.14
+            userVipService.setStartTime(DateUtil.offsetDay(vipExpireTime, 1)); //6.15
+            userVipService.setExpireTime(DateUtil.offsetMonth(userVipService.getStartTime(), serviceMonth)); //7.15
         }
-        userVipService.setOrderNo(userPaidRecordVo.getOrderNo());
-        userVipService.setUserId(userPaidRecordVo.getUserId());
         userVipServiceMapper.insert(userVipService);
-        //3.3 修改用户VIP标识及过期时间
+        //3.4 更新用户会员标识以及更新会员过期时间
         userInfo.setIsVip(1);
         userInfo.setVipExpireTime(userVipService.getExpireTime());
         userInfoMapper.updateById(userInfo);
@@ -1649,12 +1652,12 @@ public class VIPPaidRecordStrategy implements PaidRecordStrategy {
 **专辑购买项处理策略实现类**:其中Bean对象ID要跟前端提交项目类型一致
 
 ```java
-package com.atguigu.tingshu.user.strategy.impl;
+package com.atguigu.tingshu.user.pattern.impl;
 
-import cn.hutool.core.collection.CollectionUtil;
+import com.atguigu.tingshu.common.constant.SystemConstant;
 import com.atguigu.tingshu.model.user.UserPaidAlbum;
 import com.atguigu.tingshu.user.mapper.UserPaidAlbumMapper;
-import com.atguigu.tingshu.user.strategy.PaidRecordStrategy;
+import com.atguigu.tingshu.user.pattern.DeliveryStrategy;
 import com.atguigu.tingshu.vo.user.UserPaidRecordVo;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import lombok.extern.slf4j.Slf4j;
@@ -1663,39 +1666,36 @@ import org.springframework.stereotype.Component;
 
 /**
  * @author: atguigu
- * @create: 2024-08-23 10:32
+ * @create: 2025-06-16 09:31
  */
 @Slf4j
-@Component("1001")
-public class AlbumPaidRecordStrategy implements PaidRecordStrategy {
+@Component(SystemConstant.ORDER_ITEM_TYPE_ALBUM)//默认Bean的ID 为 albumDelivery
+public class AlbumDelivery implements DeliveryStrategy {
 
     @Autowired
     private UserPaidAlbumMapper userPaidAlbumMapper;
 
-
     /**
-     * 处理购买商品类型为专辑-发货逻辑
+     * 虚拟物品-专辑-发货行为实现
      * @param userPaidRecordVo
      */
     @Override
-    public void handlerPaidRecord(UserPaidRecordVo userPaidRecordVo) {
-        log.info("[用户服务]处理购买商品类型为专辑发货逻辑");
-        //2.TODO 处理购买项目类型-专辑
-        //2.1 根据订单编号查询是否处理过专辑购买记录
-        LambdaQueryWrapper<UserPaidAlbum> paidAlbumLambdaQueryWrapper = new LambdaQueryWrapper<>();
-        paidAlbumLambdaQueryWrapper.eq(UserPaidAlbum::getOrderNo, userPaidRecordVo.getOrderNo());
-        if (userPaidAlbumMapper.selectCount(paidAlbumLambdaQueryWrapper) > 0) {
+    public void delivery(UserPaidRecordVo userPaidRecordVo) {
+        //1.1 根据订单编号查询已购专辑记录,如果存在,则返回
+        Long count = userPaidAlbumMapper.selectCount(
+                new LambdaQueryWrapper<UserPaidAlbum>()
+                        .eq(UserPaidAlbum::getOrderNo, userPaidRecordVo.getOrderNo())
+        );
+        if (count > 0) {
+            log.info("已存在该订单:{}的已购专辑记录", userPaidRecordVo.getOrderNo());
             return;
         }
-        //2.2 保存专辑购买记录
-        if (CollectionUtil.isNotEmpty(userPaidRecordVo.getItemIdList())) {
-            Long albumId = userPaidRecordVo.getItemIdList().get(0);
-            UserPaidAlbum userPaidAlbum = new UserPaidAlbum();
-            userPaidAlbum.setOrderNo(userPaidRecordVo.getOrderNo());
-            userPaidAlbum.setUserId(userPaidRecordVo.getUserId());
-            userPaidAlbum.setAlbumId(albumId);
-            userPaidAlbumMapper.insert(userPaidAlbum);
-        }
+        //1.2 如果不存在,则构建专辑已购记录完成保存
+        UserPaidAlbum userPaidAlbum = new UserPaidAlbum();
+        userPaidAlbum.setOrderNo(userPaidRecordVo.getOrderNo());
+        userPaidAlbum.setUserId(userPaidRecordVo.getUserId());
+        userPaidAlbum.setAlbumId(userPaidRecordVo.getItemIdList().get(0));
+        userPaidAlbumMapper.insert(userPaidAlbum);
     }
 }
 ```
@@ -1703,14 +1703,14 @@ public class AlbumPaidRecordStrategy implements PaidRecordStrategy {
 **声音购买项处理策略实现类**:其中Bean对象ID要跟前端提交项目类型一致
 
 ```java
-package com.atguigu.tingshu.user.strategy.impl;
+package com.atguigu.tingshu.user.pattern.impl;
 
-import cn.hutool.core.collection.CollectionUtil;
 import com.atguigu.tingshu.album.AlbumFeignClient;
+import com.atguigu.tingshu.common.constant.SystemConstant;
 import com.atguigu.tingshu.model.album.TrackInfo;
 import com.atguigu.tingshu.model.user.UserPaidTrack;
 import com.atguigu.tingshu.user.mapper.UserPaidTrackMapper;
-import com.atguigu.tingshu.user.strategy.PaidRecordStrategy;
+import com.atguigu.tingshu.user.pattern.DeliveryStrategy;
 import com.atguigu.tingshu.vo.user.UserPaidRecordVo;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import lombok.extern.slf4j.Slf4j;
@@ -1721,94 +1721,112 @@ import java.util.List;
 
 /**
  * @author: atguigu
- * @create: 2024-08-23 10:32
+ * @create: 2025-06-16 09:31
  */
 @Slf4j
-@Component("1002")
-public class TrackPaidRecordStrategy implements PaidRecordStrategy {
+@Component(SystemConstant.ORDER_ITEM_TYPE_TRACK)//默认Bean的ID 为 albumDelivery
+public class TrackDelivery implements DeliveryStrategy {
 
     @Autowired
-    private AlbumFeignClient albumFeignClient;
+    private UserPaidTrackMapper userPaidTrackMapper;
 
     @Autowired
-    private UserPaidTrackMapper userPaidTrackMapper;
+    private AlbumFeignClient albumFeignClient;
 
     /**
-     * 处理购买商品类型为声音-发货逻辑
+     * 虚拟物品-声音-发货行为实现
+     *
      * @param userPaidRecordVo
      */
     @Override
-    public void handlerPaidRecord(UserPaidRecordVo userPaidRecordVo) {
-        log.info("[用户服务]处理购买商品类型为声音发货逻辑");
-        //1.根据订单编号查询是否处理过声音购买记录
-        LambdaQueryWrapper<UserPaidTrack> userPaidTrackLambdaQueryWrapper = new LambdaQueryWrapper<>();
-        userPaidTrackLambdaQueryWrapper.eq(UserPaidTrack::getOrderNo, userPaidRecordVo.getOrderNo());
-        userPaidTrackLambdaQueryWrapper.select(UserPaidTrack::getId);
-        if (userPaidTrackMapper.selectCount(userPaidTrackLambdaQueryWrapper) > 0) {
+    public void delivery(UserPaidRecordVo userPaidRecordVo) {
+        //2. 处理虚拟物品"声音"发货逻辑
+        //2.1 根据订单编号查询已购声音记录,如果存在,则返回
+        Long count = userPaidTrackMapper.selectCount(
+                new LambdaQueryWrapper<UserPaidTrack>()
+                        .eq(UserPaidTrack::getOrderNo, userPaidRecordVo.getOrderNo())
+        );
+        if (count > 0) {
+            log.info("已存在该订单:{}的已购声音记录", userPaidRecordVo.getOrderNo());
             return;
         }
-        //2.保存声音购买记录
-        //2.1 根据声音ID查询声音信息获取所属专辑ID
+        //2.2 如果不存在,则构建声音已购记录完成保存
+        TrackInfo trackInfo = albumFeignClient.getTrackInfo(userPaidRecordVo.getItemIdList().get(0)).getData();
         List<Long> trackIdList = userPaidRecordVo.getItemIdList();
-        if (CollectionUtil.isNotEmpty(trackIdList)) {
-            TrackInfo trackInfo = albumFeignClient.getTrackInfo(trackIdList.get(0)).getData();
-            Long albumId = trackInfo.getAlbumId();
-            for (Long trackId : trackIdList) {
-                UserPaidTrack userPaidTrack = new UserPaidTrack();
-                userPaidTrack.setAlbumId(albumId);
-                userPaidTrack.setOrderNo(userPaidRecordVo.getOrderNo());
-                userPaidTrack.setTrackId(trackId);
-                userPaidTrack.setUserId(userPaidRecordVo.getUserId());
-                userPaidTrackMapper.insert(userPaidTrack);
-            }
+        for (Long trackId : trackIdList) {
+            UserPaidTrack userPaidTrack = new UserPaidTrack();
+            userPaidTrack.setOrderNo(userPaidRecordVo.getOrderNo());
+            userPaidTrack.setUserId(userPaidRecordVo.getUserId());
+            userPaidTrack.setAlbumId(trackInfo.getAlbumId());
+            userPaidTrack.setTrackId(trackId);
+            userPaidTrackMapper.insert(userPaidTrack);
         }
     }
 }
+
 ```
 
 **提供工厂类**组装所有的策略实现类
 
 ```java
-package com.atguigu.tingshu.user.strategy.fac;
+package com.atguigu.tingshu.user.pattern.impl;
 
-import com.atguigu.tingshu.user.strategy.PaidRecordStrategy;
-import com.atguigu.tingshu.user.strategy.impl.AlbumPaidRecordStrategy;
-import com.atguigu.tingshu.user.strategy.impl.TrackPaidRecordStrategy;
+import com.atguigu.tingshu.album.AlbumFeignClient;
+import com.atguigu.tingshu.common.constant.SystemConstant;
+import com.atguigu.tingshu.model.album.TrackInfo;
+import com.atguigu.tingshu.model.user.UserPaidTrack;
+import com.atguigu.tingshu.user.mapper.UserPaidTrackMapper;
+import com.atguigu.tingshu.user.pattern.DeliveryStrategy;
+import com.atguigu.tingshu.vo.user.UserPaidRecordVo;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
-import javax.swing.*;
-import java.util.Map;
+import java.util.List;
 
 /**
  * @author: atguigu
- * @create: 2024-08-23 10:35
+ * @create: 2025-06-16 09:31
  */
 @Slf4j
-@Component
-public class StrategyFactory {
+@Component(SystemConstant.ORDER_ITEM_TYPE_TRACK)//默认Bean的ID 为 albumDelivery
+public class TrackDelivery implements DeliveryStrategy {
 
-    /**
-     * 通过Spring注入机制将PaidRecordStrategy接口下所有实现类对象自动注入到Map
-     * Map中Key 为策略类型-策略实现类Bean名称(1001,1002,1003)
-     * Map中Value 具体策略实现类对象
-     */
     @Autowired
-    private Map<String, PaidRecordStrategy> strategyMap;
+    private UserPaidTrackMapper userPaidTrackMapper;
 
+    @Autowired
+    private AlbumFeignClient albumFeignClient;
 
     /**
-     * 根据策略类型获取对应策略实现类对象
+     * 虚拟物品-声音-发货行为实现
      *
-     * @param strategy
-     * @return
+     * @param userPaidRecordVo
      */
-    public PaidRecordStrategy getStrategy(String strategy) {
-        if (strategyMap.containsKey(strategy)) {
-            return strategyMap.get(strategy);
+    @Override
+    public void delivery(UserPaidRecordVo userPaidRecordVo) {
+        //2. 处理虚拟物品"声音"发货逻辑
+        //2.1 根据订单编号查询已购声音记录,如果存在,则返回
+        Long count = userPaidTrackMapper.selectCount(
+                new LambdaQueryWrapper<UserPaidTrack>()
+                        .eq(UserPaidTrack::getOrderNo, userPaidRecordVo.getOrderNo())
+        );
+        if (count > 0) {
+            log.info("已存在该订单:{}的已购声音记录", userPaidRecordVo.getOrderNo());
+            return;
+        }
+        //2.2 如果不存在,则构建声音已购记录完成保存
+        TrackInfo trackInfo = albumFeignClient.getTrackInfo(userPaidRecordVo.getItemIdList().get(0)).getData();
+        List<Long> trackIdList = userPaidRecordVo.getItemIdList();
+        for (Long trackId : trackIdList) {
+            UserPaidTrack userPaidTrack = new UserPaidTrack();
+            userPaidTrack.setOrderNo(userPaidRecordVo.getOrderNo());
+            userPaidTrack.setUserId(userPaidRecordVo.getUserId());
+            userPaidTrack.setAlbumId(trackInfo.getAlbumId());
+            userPaidTrack.setTrackId(trackId);
+            userPaidTrackMapper.insert(userPaidTrack);
         }
-        throw new RuntimeException("策略类型不存在");
     }
 }
 ```
@@ -1817,19 +1835,21 @@ public class StrategyFactory {
 
 ```java
 @Autowired
-private StrategyFactory strategyFactory;
+private DeliveryStrategyFactory deliveryStrategyFactory;
 
 /**
- * 保存用户不同购买项目类型的购买记录
+ * 用户支付成功后,虚拟物品发货
  *
  * @param userPaidRecordVo
+ * @return
  */
 @Override
 public void savePaidRecord(UserPaidRecordVo userPaidRecordVo) {
-    //1001-专辑 1002-声音 1003-vip会员
-    String itemType = userPaidRecordVo.getItemType();
-    PaidRecordStrategy strategy = strategyFactory.getStrategy(itemType);
-    strategy.handlerPaidRecord(userPaidRecordVo);
+    //TODO 策略模式+工厂模式优化
+    //1. 根据商品类型从工厂中返回具体策略实现类对象
+    DeliveryStrategy strategy = deliveryStrategyFactory.getStrategy(userPaidRecordVo.getItemType());
+    //2. 调用策略实现类对象进行虚拟物品发货逻辑
+    strategy.delivery(userPaidRecordVo);
 }
 ```
 
@@ -1883,23 +1903,19 @@ OrderInfo saveOrderInfo(Long userId, OrderInfoVo orderInfoVo);
 ```
 
 ```java
-@Autowired
-private AccountFeignClient accountFeignClient;
-
 /**
- * 提交订单
+ * 提交订单,支持付款方式(微信、余额)
  *
- * @param userId      用户ID
- * @param orderInfoVo 订单vo信息
- * @return {orderNo:"订单编号"}
+ * @param orderInfoVo 订单vo
+ * @return {orderNo:"订单唯一编号"}
  */
 @Override
 @GlobalTransactional(rollbackFor = Exception.class)
-public Map<String, String> submitOrder(Long userId, OrderInfoVo orderInfoVo) {
-    //1.业务校验:验证流水号是否一致 避免用户误触回退按钮导致订单重复提交
-    //1.1 确保判断流水号删除流水号原子性采用lua脚本来实现
+public Map<String, String> submitOrder(OrderInfoVo orderInfoVo) {
+    Long userId = AuthContextHolder.getUserId();
+    //1.业务校验1,验证流水号,防止订单重复提交 采用Lua脚本判断删除原子性
     String tradeKey = RedisConstant.ORDER_TRADE_NO_PREFIX + userId;
-    //1.2 创建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" +
@@ -1909,54 +1925,74 @@ public Map<String, String> submitOrder(Long userId, OrderInfoVo orderInfoVo) {
     DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>(script, Boolean.class);
     Boolean flag = (Boolean) redisTemplate.execute(redisScript, Arrays.asList(tradeKey), orderInfoVo.getTradeNo());
     if (!flag) {
-        throw new GuiguException(500, "流水号校验失败请重试");
+        throw new GuiguException(500, "验证流水号失败");
     }
-    //2.业务校验:验证签名是否一致 避免用户篡改订单vo中参数导致商户利益损害
-    //2.1 将提交订单VO转为Map用于验证签名
+
+    //2.业务校验2,验证签名,防止订单确认页中订单数据(商品、金额、数量)被篡改
+    //2.1 将参与签名订单VO转为Map,TODO 加签时付款方式属性payWay未参与签名
     Map<String, Object> orderInfoMap = BeanUtil.beanToMap(orderInfoVo);
-    //2.2 将Map中的payWay属性去除 在订单确认页生成签名没有加入payWay属性
+    //2.2 手动删除掉签名属性payWay
     orderInfoMap.remove("payWay");
+    //2.3 验签
     SignHelper.checkSign(orderInfoMap);
-    //3.核心业务1:保存订单相关信息(订单、订单明细、订单减免) 订单状态:未支付
-    OrderInfo orderInfo = this.saveOrderInfo(userId, orderInfoVo);
 
-    //4.判断付款方式-如果是账户余额付款
-    if (SystemConstant.ORDER_PAY_ACCOUNT.equals(orderInfoVo.getPayWay())) {
-        //4.1.核心业务2:余额扣减
-        //4.1.1 构建扣减余额VO对象
+    //TODO 后续核心业务处理
+    //3. 保存订单,返回订单对象 此时订单支付状态:未支付
+    OrderInfo orderInfo = this.saveOrderInfo(orderInfoVo, userId);
+
+    //支付方式:1101-微信 1102-支付宝 1103-账户余额
+    String payWay = orderInfoVo.getPayWay();
+
+    //4.TODO 处理付款方式为余额
+    if (SystemConstant.ORDER_PAY_ACCOUNT.equals(payWay)) {
+        //4.1 远程调用"账户服务",检查扣减余额
+        //4.1.1 构建扣减账户余额VO对象AccountDeductVo
         AccountDeductVo accountDeductVo = new AccountDeductVo();
         accountDeductVo.setOrderNo(orderInfo.getOrderNo());
         accountDeductVo.setUserId(orderInfo.getUserId());
         accountDeductVo.setAmount(orderInfo.getOrderAmount());
         accountDeductVo.setContent(orderInfo.getOrderTitle());
-        //4.1.2 远程调用账户服务扣减余额 一定要判断Feign调用结果业务状态码
+
+        //4.1.2 远程调用
         Result result = accountFeignClient.checkAndDeduct(accountDeductVo);
-        if (!result.getCode().equals(200)) {
-            throw new GuiguException(500, "账户余额不足");
+        //4.1.3 判断远程调用响应业务状态码,如果不是200抛出异常,触发全局事务回滚
+        if (result.getCode().intValue() != 200) {
+            //4.2 扣减余额失败,业务终止,回滚全局事务
+            throw new GuiguException(result.getCode(), result.getMessage());
         }
-
-        //扣减余额成功后,修改订单状态:已支付
+        //4.3 扣减余额成功,订单状态改为已支付
         orderInfo.setOrderStatus(SystemConstant.ORDER_STATUS_PAID);
         orderInfoMapper.updateById(orderInfo);
 
-        //4.2.核心业务3:虚拟物品发货
-        //4.2.1 构建虚拟物品发货VO对象
+        //4.4 远程调用"用户服务",虚拟物品发货(增加购买记录)
+        //4.4.1 构建虚拟物品发货VO对象UserPaidRecordVo
         UserPaidRecordVo userPaidRecordVo = new UserPaidRecordVo();
         userPaidRecordVo.setOrderNo(orderInfo.getOrderNo());
         userPaidRecordVo.setUserId(orderInfo.getUserId());
         userPaidRecordVo.setItemType(orderInfo.getItemType());
-        List<Long> itemIdList = orderInfoVo.getOrderDetailVoList().stream().map(OrderDetailVo::getItemId).collect(Collectors.toList());
-        userPaidRecordVo.setItemIdList(itemIdList);
-        //4.2.2 远程调用用户服务虚拟物品发货 一定要判断Feign调用结果业务状态码
-        result = userFeignClient.savePaidRecord(userPaidRecordVo);
-        if (!result.getCode().equals(200)) {
-            throw new GuiguException(500, "虚拟物品发货异常");
+        List<OrderDetailVo> orderDetailVoList = orderInfoVo.getOrderDetailVoList();
+        if (CollUtil.isNotEmpty(orderDetailVoList)) {
+            List<Long> itemIdList = orderDetailVoList
+                    .stream()
+                    .map(OrderDetailVo::getItemId)
+                    .collect(Collectors.toList());
+            userPaidRecordVo.setItemIdList(itemIdList);
+            //4.4.2 远程调用
+            result = userFeignClient.savePaidRecord(userPaidRecordVo);
+            if (result.getCode().intValue() != 200) {
+                //4.2 虚拟物品发货失败,业务终止,回滚全局事务
+                throw new GuiguException(result.getCode(), result.getMessage());
+            }
         }
     }
-    //5.TODO 判断付款方式-如果是微信付款,采用延迟消息完成延迟关单
-    if (SystemConstant.ORDER_PAY_WAY_WEIXIN.equals(orderInfoVo.getPayWay())) {
-
+    //5.处理付款方式为微信
+    if (SystemConstant.ORDER_PAY_WAY_WEIXIN.equals(payWay)) {
+        //5.1 发送延迟关单消息,自动关闭超时未支付订单
+        int delayTime = 60 * 15;
+        rabbitService.sendDelayMessage(MqConst.EXCHANGE_CANCEL_ORDER, MqConst.ROUTING_CANCEL_ORDER, orderInfo.getId(), delayTime);
     }
+
+    //x.封装响应结果 如果是微信支付,根据响应订单编号对接微信支付;如果是余额支付成功,返回订单编号,查询订单详情
     Map<String, String> map = new HashMap<>();
     map.put("orderNo", orderInfo.getOrderNo());
     return map;
@@ -1993,51 +2029,53 @@ private OrderDetailMapper orderDetailMapper;
 private OrderDerateMapper orderDerateMapper;
 
 /**
- * 保存订单相关信息(订单、订单明细、订单减免)
+ * 保存订单相关信息(订单、订单明细、优惠明细)
  *
- * @param userId
  * @param orderInfoVo
+ * @param userId
  * @return
  */
 @Override
-public OrderInfo saveOrderInfo(Long userId, OrderInfoVo orderInfoVo) {
-    //1.保存订单信息
+public OrderInfo saveOrderInfo(OrderInfoVo orderInfoVo, Long userId) {
+    //1.封装订单对象,保存订单
     OrderInfo orderInfo = BeanUtil.copyProperties(orderInfoVo, OrderInfo.class);
-    //1.1 设置订单状态设置为:未支付
-    orderInfo.setOrderStatus(SystemConstant.ORDER_STATUS_UNPAID);
-    //1.2 设置用户ID
+    //1.1 设置用户ID
     orderInfo.setUserId(userId);
-    //1.3 设置订单名称(从订单项获取名称)
+    //1.2 设置订单标题
     List<OrderDetailVo> orderDetailVoList = orderInfoVo.getOrderDetailVoList();
-    if (CollectionUtil.isNotEmpty(orderDetailVoList)) {
-        orderInfo.setOrderTitle(orderDetailVoList.get(0).getItemName());
+    if (CollUtil.isNotEmpty(orderDetailVoList)) {
+        OrderDetailVo orderDetailVo = orderDetailVoList.get(0);
+        orderInfo.setOrderTitle(orderDetailVo.getItemName());
     }
-    //1.4 设置订单编号(确保全局唯一用于对接支付平台) 年月日+雪花算法
-    String oderNo = DateUtil.today().replaceAll("-", "") + IdUtil.getSnowflake(1, 1).nextIdStr();
-    orderInfo.setOrderNo(oderNo);
+    //1.3 设置订单编号 确保全局唯一且趋势递增 生成策略=当日+雪花算法
+    String orderNo = DateUtil.today().replaceAll("-", "") + IdUtil.getSnowflakeNextId();
+    orderInfo.setOrderNo(orderNo);
+    //1.4 设置订单状态 订单状态:0901-未支付 0902-已支付 0903-已取消
+    orderInfo.setOrderStatus(SystemConstant.ORDER_STATUS_UNPAID);
+    //1.5 保存订单
     orderInfoMapper.insert(orderInfo);
     Long orderId = orderInfo.getId();
-    //2.保存订单明细列表
-    //2.1 获取订单明细列表
-    if (CollectionUtil.isNotEmpty(orderDetailVoList)) {
-        for (OrderDetailVo orderDetailVo : orderDetailVoList) {
-            OrderDetail orderDetail = BeanUtil.copyProperties(orderDetailVo, OrderDetail.class);
-            //2.1 关联订单
-            orderDetail.setOrderId(orderId);
-            //2.2 保存订单明细
-            orderDetailMapper.insert(orderDetail);
-        }
+
+    //2.封装订单明细对象,保存订单明细
+    if (CollUtil.isNotEmpty(orderDetailVoList)) {
+        List<OrderDetail> orderDetailList = orderDetailVoList
+                .stream()
+                .map(orderDetailVo -> {
+                    OrderDetail orderDetail = BeanUtil.copyProperties(orderDetailVo, OrderDetail.class);
+                    //将订单明细关联订单ID
+                    orderDetail.setOrderId(orderId);
+                    return orderDetail;
+                }).collect(Collectors.toList());
+        orderDetailService.saveBatch(orderDetailList);
     }
-    //3.保存订单减免列表
+    //3.封装订单优惠对象,保存订单优惠明细
     List<OrderDerateVo> orderDerateVoList = orderInfoVo.getOrderDerateVoList();
-    if (CollectionUtil.isNotEmpty(orderDerateVoList)) {
-        for (OrderDerateVo orderDerateVo : orderDerateVoList) {
+    if (CollUtil.isNotEmpty(orderDerateVoList)) {
+        orderDerateVoList.stream().forEach(orderDerateVo -> {
             OrderDerate orderDerate = BeanUtil.copyProperties(orderDerateVo, OrderDerate.class);
-            //3.1 关联订单
             orderDerate.setOrderId(orderId);
-            //3.2 保存订单减免
             orderDerateMapper.insert(orderDerate);
-        }
+        });
     }
     return orderInfo;
 }