3 Commits 3e44d0b97f ... a424c6aa0e

Author SHA1 Message Date
  it_lv a424c6aa0e day14 2 weeks ago
  it_lv 88c2231ce1 day14 2 weeks ago
  it_lv 0fbb53d266 day14 2 weeks ago
17 changed files with 757 additions and 107 deletions
  1. 16 0
      service/service-account/pom.xml
  2. 16 0
      service/service-order/pom.xml
  3. 54 0
      service/service-order/src/main/java/com/atguigu/tingshu/order/pattern/AbstractTradeOrderStrategy.java
  4. 28 0
      service/service-order/src/main/java/com/atguigu/tingshu/order/pattern/factory/TradeOrderStrategyFactory.java
  5. 119 0
      service/service-order/src/main/java/com/atguigu/tingshu/order/pattern/strategy/AlbumTradeOrderStrategy.java
  6. 80 0
      service/service-order/src/main/java/com/atguigu/tingshu/order/pattern/strategy/TrackTradeOrderStrategy.java
  7. 17 0
      service/service-order/src/main/java/com/atguigu/tingshu/order/pattern/strategy/TradeOrderStrategy.java
  8. 78 0
      service/service-order/src/main/java/com/atguigu/tingshu/order/pattern/strategy/VIPTradeOrderStrategy.java
  9. 32 16
      service/service-order/src/main/java/com/atguigu/tingshu/order/service/impl/OrderInfoServiceImpl.java
  10. 16 0
      service/service-user/pom.xml
  11. 11 91
      service/service-user/src/main/java/com/atguigu/tingshu/user/service/impl/UserInfoServiceImpl.java
  12. 17 0
      service/service-user/src/main/java/com/atguigu/tingshu/user/strategy/PaidRecordStrategy.java
  13. 52 0
      service/service-user/src/main/java/com/atguigu/tingshu/user/strategy/factory/StrategyFactory.java
  14. 45 0
      service/service-user/src/main/java/com/atguigu/tingshu/user/strategy/impl/AlbumPaidRecordStrategy.java
  15. 66 0
      service/service-user/src/main/java/com/atguigu/tingshu/user/strategy/impl/TrackPaidRecordStrategy.java
  16. 86 0
      service/service-user/src/main/java/com/atguigu/tingshu/user/strategy/impl/VIPPaidRecordStrategy.java
  17. 24 0
      service/service-user/src/main/java/com/atguigu/tingshu/user/strategy/impl/VIPProPaidRecordStrategy.java

+ 16 - 0
service/service-account/pom.xml

@@ -23,6 +23,22 @@
             <artifactId>rabbit-util</artifactId>
             <version>1.0</version>
         </dependency>
+        <!--seata-->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
+            <!-- 默认seata客户端版本比较低,排除后重新引入指定版本-->
+            <exclusions>
+                <exclusion>
+                    <groupId>io.seata</groupId>
+                    <artifactId>seata-spring-boot-starter</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>io.seata</groupId>
+            <artifactId>seata-spring-boot-starter</artifactId>
+        </dependency>
     </dependencies>
 
     <build>

+ 16 - 0
service/service-order/pom.xml

@@ -28,6 +28,22 @@
             <artifactId>service-account-client</artifactId>
             <version>1.0</version>
         </dependency>
+        <!--seata-->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
+            <!-- 默认seata客户端版本比较低,排除后重新引入指定版本-->
+            <exclusions>
+                <exclusion>
+                    <groupId>io.seata</groupId>
+                    <artifactId>seata-spring-boot-starter</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>io.seata</groupId>
+            <artifactId>seata-spring-boot-starter</artifactId>
+        </dependency>
     </dependencies>
 
     <build>

+ 54 - 0
service/service-order/src/main/java/com/atguigu/tingshu/order/pattern/AbstractTradeOrderStrategy.java

@@ -0,0 +1,54 @@
+package com.atguigu.tingshu.order.pattern;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.IdUtil;
+import com.atguigu.tingshu.common.constant.RedisConstant;
+import com.atguigu.tingshu.common.util.AuthContextHolder;
+import com.atguigu.tingshu.order.helper.SignHelper;
+import com.atguigu.tingshu.order.pattern.strategy.TradeOrderStrategy;
+import com.atguigu.tingshu.vo.order.OrderInfoVo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 策略模式抽象类:封装复用代码
+ * @author: atguigu
+ * @create: 2025-03-25 16:22
+ */
+public abstract class AbstractTradeOrderStrategy implements TradeOrderStrategy {
+
+
+    @Autowired
+    private RedisTemplate redisTemplate;
+
+    /**
+     * 为本次订单结算生成流水号
+     * @param orderInfoVo
+     */
+    protected void generateTradeNo(OrderInfoVo orderInfoVo) {
+        Long userId = AuthContextHolder.getUserId();
+        String tradeNo = IdUtil.randomUUID();
+        String tradeKey = RedisConstant.ORDER_TRADE_NO_PREFIX + userId;
+        redisTemplate.opsForValue().set(tradeKey, tradeNo, RedisConstant.ORDER_TRADE_EXPIRE, TimeUnit.MINUTES);
+        orderInfoVo.setTradeNo(tradeNo);
+    }
+
+
+    /**
+     * 为本次订单生成签名值
+     * @param orderInfoVo
+     */
+    protected void generateSign(OrderInfoVo orderInfoVo) {
+        orderInfoVo.setTimestamp(System.currentTimeMillis());
+        //将订单VO转为Map TODO:VO中包含付款方式"payWay"此时为空 生成签名将排除掉payWay字段,故暂时不参与签名
+        Map<String, Object> map = BeanUtil.beanToMap(orderInfoVo, false, true);
+        String sign = SignHelper.getSign(map);
+        orderInfoVo.setSign(sign);
+    }
+
+
+
+}

+ 28 - 0
service/service-order/src/main/java/com/atguigu/tingshu/order/pattern/factory/TradeOrderStrategyFactory.java

@@ -0,0 +1,28 @@
+package com.atguigu.tingshu.order.pattern.factory;
+
+import com.atguigu.tingshu.order.pattern.strategy.TradeOrderStrategy;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+/**
+ * @author: atguigu
+ * @create: 2025-03-25 16:23
+ */
+@Component
+public class TradeOrderStrategyFactory {
+
+    @Autowired
+    private Map<String, TradeOrderStrategy> tradeOrderStrategyMap;
+
+
+    /**
+     * 根据商品类别返回策略实现类对象
+     * @param type
+     * @return
+     */
+    public TradeOrderStrategy getTradeOrderStrategy(String type) {
+        return tradeOrderStrategyMap.get(type);
+    }
+}

+ 119 - 0
service/service-order/src/main/java/com/atguigu/tingshu/order/pattern/strategy/AlbumTradeOrderStrategy.java

@@ -0,0 +1,119 @@
+package com.atguigu.tingshu.order.pattern.strategy;
+
+import cn.hutool.core.lang.Assert;
+import com.atguigu.tingshu.album.AlbumFeignClient;
+import com.atguigu.tingshu.common.util.AuthContextHolder;
+import com.atguigu.tingshu.model.album.AlbumInfo;
+import com.atguigu.tingshu.order.pattern.AbstractTradeOrderStrategy;
+import com.atguigu.tingshu.user.client.UserFeignClient;
+import com.atguigu.tingshu.vo.order.OrderDerateVo;
+import com.atguigu.tingshu.vo.order.OrderDetailVo;
+import com.atguigu.tingshu.vo.order.OrderInfoVo;
+import com.atguigu.tingshu.vo.order.TradeVo;
+import com.atguigu.tingshu.vo.user.UserInfoVo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import static com.atguigu.tingshu.common.constant.SystemConstant.ORDER_DERATE_ALBUM_DISCOUNT;
+
+/**
+ * 商品类别为专辑:策略实现类
+ * @author: atguigu
+ * @create: 2025-03-25 16:24
+ */
+@Component("1001")
+public class AlbumTradeOrderStrategy extends AbstractTradeOrderStrategy {
+
+    @Autowired
+    private UserFeignClient userFeignClient;
+
+    @Autowired
+    private AlbumFeignClient albumFeignClient;
+    /**
+     * 处理结算商品类别为:专辑
+     * @param tradeVo
+     * @return
+     */
+    @Override
+    public void handleTrade(TradeVo tradeVo, OrderInfoVo orderInfoVo) {
+        Long userId = AuthContextHolder.getUserId();
+        //3. 处理购买商品类别:专辑
+        //3.1 远程调用"用户服务"判断是否重复购买专辑 如果是:业务终止
+        Long albumId = tradeVo.getItemId();
+        Boolean isPaid = userFeignClient.isPaidAlbum(albumId).getData();
+        Assert.state(!isPaid, "用户已购买专辑:{}", albumId);
+
+        //3.2 远程调用"专辑服务"获取专辑信息
+        AlbumInfo albumInfo = albumFeignClient.getAlbumInfo(albumId).getData();
+        Assert.notNull(albumInfo, "专辑:{}不存在", albumId);
+        //3.2.1 专辑价格
+        BigDecimal price = albumInfo.getPrice();
+        //3.2.2 普通用户折扣
+        BigDecimal discount = albumInfo.getDiscount();
+        //3.2.3 VIP用户折扣
+        BigDecimal vipDiscount = albumInfo.getVipDiscount();
+
+        //3.3 远程调用"用户服务"判断用户身份
+        Boolean isVIP = false;
+        UserInfoVo userInfoVo = userFeignClient.getUserInfoVo(userId).getData();
+        Assert.notNull(userInfoVo, "用户:{}不存在", userId);
+        if (userInfoVo.getIsVip().intValue() == 1 && userInfoVo.getVipExpireTime().after(new Date())) {
+            isVIP = true;
+        }
+        //3.4 计算相关价格
+        //3.4.1 暂时让订单价等于原价
+        BigDecimal originalAmount = price;
+        BigDecimal orderAmount = originalAmount;
+        BigDecimal derateAmount = new BigDecimal("0.00");
+        //3.4.2 如果是普通用户且专辑设置普通用户折扣,则订单价=原价*普通用户折扣
+        if (!isVIP && discount.doubleValue() != -1) {
+            orderAmount =
+                    originalAmount.multiply(discount).divide(new BigDecimal("10"), 2, RoundingMode.HALF_UP);
+            derateAmount = originalAmount.subtract(orderAmount);
+        }
+        //3.4.3 如果是VIP用户且专辑设置VIP用户折扣,则订单价=原价*VIP用户折扣
+        if (isVIP && vipDiscount.doubleValue() != -1) {
+            orderAmount =
+                    originalAmount.multiply(vipDiscount).divide(new BigDecimal("10"), 2, RoundingMode.HALF_UP);
+            derateAmount = originalAmount.subtract(orderAmount);
+        }
+        //3.5 封装订单明细列表、优惠列表
+        List<OrderDetailVo> orderDetailVoList = new ArrayList<>();
+        OrderDetailVo orderDetailVo = new OrderDetailVo();
+        orderDetailVo.setItemId(albumId);
+        orderDetailVo.setItemName("专辑:" + albumInfo.getAlbumTitle());
+        orderDetailVo.setItemUrl(albumInfo.getCoverUrl());
+        orderDetailVo.setItemPrice(originalAmount);
+        orderDetailVoList.add(orderDetailVo);
+
+        //3.6 封装订单优惠列表
+        List<OrderDerateVo> orderDerateVoList = new ArrayList<>();
+
+        if (originalAmount.compareTo(orderAmount) == 1) {
+            OrderDerateVo orderDerateVo = new OrderDerateVo();
+            orderDerateVo.setDerateType(ORDER_DERATE_ALBUM_DISCOUNT);
+            orderDerateVo.setDerateAmount(derateAmount);
+            orderDerateVo.setRemarks("专辑限时减免:" + derateAmount);
+            orderDerateVoList.add(orderDerateVo);
+        }
+
+        orderInfoVo.setOriginalAmount(originalAmount);
+        orderInfoVo.setOrderAmount(orderAmount);
+        orderInfoVo.setDerateAmount(derateAmount);
+        //5.2 为商品列表、优惠列表赋值
+        orderInfoVo.setOrderDetailVoList(orderDetailVoList);
+        orderInfoVo.setOrderDerateVoList(orderDerateVoList);
+
+        //5.3 为订单VO其他杂项赋值:购买商品类别
+        orderInfoVo.setItemType(tradeVo.getItemType());
+        //5.4 流水号、时间戳及签名
+        this.generateTradeNo(orderInfoVo);
+        this.generateSign(orderInfoVo);
+    }
+}

+ 80 - 0
service/service-order/src/main/java/com/atguigu/tingshu/order/pattern/strategy/TrackTradeOrderStrategy.java

@@ -0,0 +1,80 @@
+package com.atguigu.tingshu.order.pattern.strategy;
+
+import cn.hutool.core.lang.Assert;
+import com.atguigu.tingshu.album.AlbumFeignClient;
+import com.atguigu.tingshu.model.album.AlbumInfo;
+import com.atguigu.tingshu.model.album.TrackInfo;
+import com.atguigu.tingshu.order.pattern.AbstractTradeOrderStrategy;
+import com.atguigu.tingshu.vo.order.OrderDerateVo;
+import com.atguigu.tingshu.vo.order.OrderDetailVo;
+import com.atguigu.tingshu.vo.order.OrderInfoVo;
+import com.atguigu.tingshu.vo.order.TradeVo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 商品类别为声音:策略实现类
+ * @author: atguigu
+ * @create: 2025-03-25 16:24
+ */
+@Component("1002")
+public class TrackTradeOrderStrategy extends AbstractTradeOrderStrategy {
+
+    @Autowired
+    private AlbumFeignClient albumFeignClient;
+    /**
+     * 处理结算商品类别为:声音
+     * @param tradeVo
+     * @return
+     */
+    @Override
+    public void handleTrade(TradeVo tradeVo, OrderInfoVo orderInfoVo) {
+        //4.处理购买商品类别:声音
+        //4.1 远程调用"专辑服务"获取待结算声音列表
+        Long trackId = tradeVo.getItemId();
+        List<TrackInfo> waitBuyTrackList = albumFeignClient.getWaitBuyTrackList(trackId, tradeVo.getTrackCount()).getData();
+        Assert.notNull(waitBuyTrackList, "不存在待结算商品");
+
+        //4.2 远程调用"专辑服务"查询专辑信息,得到声音单价
+        Long albumId = waitBuyTrackList.get(0).getAlbumId();
+        AlbumInfo albumInfo = albumFeignClient.getAlbumInfo(albumId).getData();
+        Assert.notNull(albumInfo, "声音所属专辑:{}不存在", albumId);
+        BigDecimal price = albumInfo.getPrice();
+
+        //4.3 计算相关价格 声音不支持折扣
+        BigDecimal originalAmount = price.multiply(BigDecimal.valueOf(tradeVo.getTrackCount()));
+        BigDecimal orderAmount = originalAmount;
+        BigDecimal derateAmount = new BigDecimal("0.00");
+        //4.4 封装订单明细列表
+        List<OrderDetailVo> orderDetailVoList = waitBuyTrackList
+                .stream()
+                .map(trackInfo -> {
+                    OrderDetailVo orderDetailVo = new OrderDetailVo();
+                    orderDetailVo.setItemId(trackInfo.getId());
+                    orderDetailVo.setItemName("声音:" + trackInfo.getTrackTitle());
+                    orderDetailVo.setItemUrl(trackInfo.getCoverUrl());
+                    orderDetailVo.setItemPrice(price);
+                    return orderDetailVo;
+                }).collect(Collectors.toList());
+        //4.5 即使声音无优惠,也要封装一个订单优惠列表 否则验签会失败
+        List<OrderDerateVo> orderDerateVoList = new ArrayList<>();
+
+        orderInfoVo.setOriginalAmount(originalAmount);
+        orderInfoVo.setOrderAmount(orderAmount);
+        orderInfoVo.setDerateAmount(derateAmount);
+        //5.2 为商品列表赋值
+        orderInfoVo.setOrderDetailVoList(orderDetailVoList);
+        orderInfoVo.setOrderDerateVoList(orderDerateVoList);
+
+        //5.3 为订单VO其他杂项赋值:购买商品类别
+        orderInfoVo.setItemType(tradeVo.getItemType());
+        //5.4 流水号、时间戳及签名
+        this.generateTradeNo(orderInfoVo);
+        this.generateSign(orderInfoVo);
+    }
+}

+ 17 - 0
service/service-order/src/main/java/com/atguigu/tingshu/order/pattern/strategy/TradeOrderStrategy.java

@@ -0,0 +1,17 @@
+package com.atguigu.tingshu.order.pattern.strategy;
+
+import com.atguigu.tingshu.vo.order.OrderInfoVo;
+import com.atguigu.tingshu.vo.order.TradeVo;
+
+/**
+ * 订单结算抽象接口
+ */
+public interface TradeOrderStrategy {
+
+    /**
+     * 不同商品订单结算
+     * @param tradeVo
+     * @return
+     */
+    public void handleTrade(TradeVo tradeVo, OrderInfoVo orderInfoVo);
+}

+ 78 - 0
service/service-order/src/main/java/com/atguigu/tingshu/order/pattern/strategy/VIPTradeOrderStrategy.java

@@ -0,0 +1,78 @@
+package com.atguigu.tingshu.order.pattern.strategy;
+
+import cn.hutool.core.lang.Assert;
+import com.atguigu.tingshu.model.user.VipServiceConfig;
+import com.atguigu.tingshu.order.pattern.AbstractTradeOrderStrategy;
+import com.atguigu.tingshu.user.client.UserFeignClient;
+import com.atguigu.tingshu.vo.order.OrderDerateVo;
+import com.atguigu.tingshu.vo.order.OrderDetailVo;
+import com.atguigu.tingshu.vo.order.OrderInfoVo;
+import com.atguigu.tingshu.vo.order.TradeVo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.atguigu.tingshu.common.constant.SystemConstant.ORDER_DERATE_VIP_SERVICE_DISCOUNT;
+
+/**
+ * 商品类别为VIP:策略实现类
+ * @author: atguigu
+ * @create: 2025-03-25 16:24
+ */
+@Component("1003")
+public class VIPTradeOrderStrategy extends AbstractTradeOrderStrategy {
+
+    @Autowired
+    private UserFeignClient userFeignClient;
+
+    /**
+     * 处理结算商品类别为:VIP会员
+     * @param tradeVo
+     * @return
+     */
+    @Override
+    public void handleTrade(TradeVo tradeVo, OrderInfoVo orderInfoVo) {
+        //2.1 远程调用"用户服务"获取套餐详情
+        VipServiceConfig vipServiceConfig = userFeignClient.getVipServiceConfig(tradeVo.getItemId()).getData();
+        Assert.notNull(vipServiceConfig, "套餐:{}不存在", tradeVo.getItemId());
+        //2.2 计算订单相关价格 订单价=原价-减免价
+        BigDecimal originalAmount = vipServiceConfig.getPrice();
+        BigDecimal orderAmount = vipServiceConfig.getDiscountPrice();
+        BigDecimal derateAmount = originalAmount.subtract(orderAmount);
+
+        //2.3 封装订单明细列表
+        List<OrderDetailVo> orderDetailVoList = new ArrayList<>();
+        OrderDetailVo orderDetailVo = new OrderDetailVo();
+        orderDetailVo.setItemId(tradeVo.getItemId());
+        orderDetailVo.setItemName("VIP套餐:" + vipServiceConfig.getName());
+        orderDetailVo.setItemUrl(vipServiceConfig.getImageUrl());
+        orderDetailVo.setItemPrice(originalAmount);
+        orderDetailVoList.add(orderDetailVo);
+
+        //2.4 封装订单优惠列表
+        List<OrderDerateVo> orderDerateVoList = new ArrayList<>();
+        if (originalAmount.compareTo(orderAmount) == 1) {
+            OrderDerateVo orderDerateVo = new OrderDerateVo();
+            //订单减免类型 1405-专辑折扣 1406-VIP服务折
+            orderDerateVo.setDerateType(ORDER_DERATE_VIP_SERVICE_DISCOUNT);
+            orderDerateVo.setDerateAmount(derateAmount);
+            orderDerateVo.setRemarks("套餐限时减免:" + derateAmount);
+            orderDerateVoList.add(orderDerateVo);
+        }
+        orderInfoVo.setOriginalAmount(originalAmount);
+        orderInfoVo.setOrderAmount(orderAmount);
+        orderInfoVo.setDerateAmount(derateAmount);
+        //5.2 为商品列表、优惠列表赋值
+        orderInfoVo.setOrderDetailVoList(orderDetailVoList);
+        orderInfoVo.setOrderDerateVoList(orderDerateVoList);
+
+        //5.3 为订单VO其他杂项赋值:购买商品类别
+        orderInfoVo.setItemType(tradeVo.getItemType());
+        //5.4 流水号、时间戳及签名
+        this.generateTradeNo(orderInfoVo);
+        this.generateSign(orderInfoVo);
+    }
+}

+ 32 - 16
service/service-order/src/main/java/com/atguigu/tingshu/order/service/impl/OrderInfoServiceImpl.java

@@ -3,24 +3,21 @@ package com.atguigu.tingshu.order.service.impl;
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.date.DateUtil;
-import cn.hutool.core.lang.Assert;
-import cn.hutool.core.util.IdUtil;
 import com.atguigu.tingshu.account.AccountFeignClient;
 import com.atguigu.tingshu.album.AlbumFeignClient;
 import com.atguigu.tingshu.common.constant.RedisConstant;
 import com.atguigu.tingshu.common.execption.GuiguException;
 import com.atguigu.tingshu.common.result.Result;
 import com.atguigu.tingshu.common.util.AuthContextHolder;
-import com.atguigu.tingshu.model.album.AlbumInfo;
-import com.atguigu.tingshu.model.album.TrackInfo;
 import com.atguigu.tingshu.model.order.OrderDerate;
 import com.atguigu.tingshu.model.order.OrderDetail;
 import com.atguigu.tingshu.model.order.OrderInfo;
-import com.atguigu.tingshu.model.user.VipServiceConfig;
 import com.atguigu.tingshu.order.helper.SignHelper;
 import com.atguigu.tingshu.order.mapper.OrderDerateMapper;
 import com.atguigu.tingshu.order.mapper.OrderDetailMapper;
 import com.atguigu.tingshu.order.mapper.OrderInfoMapper;
+import com.atguigu.tingshu.order.pattern.factory.TradeOrderStrategyFactory;
+import com.atguigu.tingshu.order.pattern.strategy.TradeOrderStrategy;
 import com.atguigu.tingshu.order.service.OrderDetailService;
 import com.atguigu.tingshu.order.service.OrderInfoService;
 import com.atguigu.tingshu.user.client.UserFeignClient;
@@ -29,20 +26,19 @@ import com.atguigu.tingshu.vo.order.OrderDerateVo;
 import com.atguigu.tingshu.vo.order.OrderDetailVo;
 import com.atguigu.tingshu.vo.order.OrderInfoVo;
 import com.atguigu.tingshu.vo.order.TradeVo;
-import com.atguigu.tingshu.vo.user.UserInfoVo;
 import com.atguigu.tingshu.vo.user.UserPaidRecordVo;
 import com.baomidou.mybatisplus.core.toolkit.IdWorker;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import io.seata.spring.annotation.GlobalTransactional;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.core.script.DefaultRedisScript;
 import org.springframework.stereotype.Service;
 
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.util.*;
-import java.util.concurrent.TimeUnit;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 import static com.atguigu.tingshu.common.constant.SystemConstant.*;
@@ -76,7 +72,21 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
     @Autowired
     private AccountFeignClient accountFeignClient;
 
+    @Autowired
+    private TradeOrderStrategyFactory tradeOrderStrategyFactory;
+
 
+    @Override
+    public OrderInfoVo trade(Long userId, TradeVo tradeVo) {
+        //1.创建订单VO
+        OrderInfoVo orderInfoVo = new OrderInfoVo();
+        //2.获取购买商品类别:付款项目类型: 1001-专辑 1002-声音 1003-vip会员
+        String itemType = tradeVo.getItemType();
+        //3.从工厂获取策略实现类对象
+        TradeOrderStrategy tradeOrderStrategy = tradeOrderStrategyFactory.getTradeOrderStrategy(itemType);
+        tradeOrderStrategy.handleTrade(tradeVo, orderInfoVo);
+        return orderInfoVo;
+    }
     /**
      * 对购买商品(VIP会员、专辑、声音)封装订单结算页所需要数据
      *
@@ -84,11 +94,13 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
      * @param tradeVo {购买商品类别,商品ID,声音数量}
      * @return
      */
-    @Override
+    /*@Override
     public OrderInfoVo trade(Long userId, TradeVo tradeVo) {
         //1.创建订单VO
         OrderInfoVo orderInfoVo = new OrderInfoVo();
 
+        //获取购买商品类别:付款项目类型: 1001-专辑 1002-声音 1003-vip会员
+        String itemType = tradeVo.getItemType();
         //1.1.初始化订单结算页三个价格:原价、减免价、订单价
         BigDecimal originalAmount = new BigDecimal("0.00");
         BigDecimal derateAmount = new BigDecimal("0.00");
@@ -97,8 +109,6 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
         List<OrderDetailVo> orderDetailVoList = new ArrayList<>();
         List<OrderDerateVo> orderDerateVoList = new ArrayList<>();
 
-        //获取购买商品类别:付款项目类型: 1001-专辑 1002-声音 1003-vip会员
-        String itemType = tradeVo.getItemType();
         //2.处理购买商品类别:VIP套餐
         if (ORDER_ITEM_TYPE_VIP.equals(itemType)) {
             //2.1 远程调用"用户服务"获取套餐详情
@@ -237,7 +247,7 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
         orderInfoVo.setSign(sign);
         //6.响应订单VO
         return orderInfoVo;
-    }
+    }*/
 
     /**
      * 提交订单
@@ -246,6 +256,7 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
      * @return {orderNo:订单编号}
      */
     @Override
+    @GlobalTransactional(rollbackFor = Exception.class)
     public String submitOrder(Long userId, OrderInfoVo orderInfoVo) {
         //1.业务校验-验证流水号 防止回退造成订单重复提交
         //1.1 从Redis中获取当前用户存入流水号值
@@ -290,7 +301,7 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
             accountDeductVo.setContent(orderInfo.getOrderTitle());
             //4.1.2 远程调用完成扣减余额
             Result result = accountFeignClient.checkAndDeduct(accountDeductVo);
-            //4.1.3 判断业务状态码是否为200
+            //4.1.3 判断业务状态码是否为200 TODO 如果这里不判断响应业务状态码 下游系统调用发生异常,由于存在全局异常处理,正常响应Result Http请求状态码200
             if (!result.getCode().equals(200)) {
                 throw new GuiguException(result.getCode(), result.getMessage());
             }
@@ -315,8 +326,12 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
                 if (!result.getCode().equals(200)) {
                     throw new GuiguException(result.getCode(), result.getMessage());
                 }
-
             }
+
+            //模拟异常
+            //if (orderInfo.getId() % 2 == 0) {
+            //    int i = 1 / 0;
+            //}
         }
         if (ORDER_PAY_WAY_WEIXIN.equals(payWay)) {
             //5. TODO 处理付款方式为:微信支付 采用延迟消息进行延迟关单
@@ -334,6 +349,7 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
      * @return
      */
     @Override
+
     public OrderInfo saveOrderInfo(OrderInfoVo orderInfoVo) {
         //1.保存订单信息
         //1.1 将订单VO拷贝到订单PO对象中

+ 16 - 0
service/service-user/pom.xml

@@ -37,6 +37,22 @@
             <artifactId>wx-java-miniapp-spring-boot-starter</artifactId>
             <version>4.5.0</version>
         </dependency>
+        <!--seata-->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
+            <!-- 默认seata客户端版本比较低,排除后重新引入指定版本-->
+            <exclusions>
+                <exclusion>
+                    <groupId>io.seata</groupId>
+                    <artifactId>seata-spring-boot-starter</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>io.seata</groupId>
+            <artifactId>seata-spring-boot-starter</artifactId>
+        </dependency>
     </dependencies>
 
     <build>

+ 11 - 91
service/service-user/src/main/java/com/atguigu/tingshu/user/service/impl/UserInfoServiceImpl.java

@@ -5,20 +5,21 @@ import cn.binarywang.wx.miniapp.api.WxMaUserService;
 import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.date.DateTime;
 import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.util.IdUtil;
 import com.atguigu.tingshu.album.AlbumFeignClient;
 import com.atguigu.tingshu.common.constant.RedisConstant;
-import com.atguigu.tingshu.common.constant.SystemConstant;
 import com.atguigu.tingshu.common.rabbit.constant.MqConst;
 import com.atguigu.tingshu.common.rabbit.service.RabbitService;
 import com.atguigu.tingshu.common.util.AuthContextHolder;
-import com.atguigu.tingshu.model.album.TrackInfo;
-import com.atguigu.tingshu.model.user.*;
+import com.atguigu.tingshu.model.user.UserInfo;
+import com.atguigu.tingshu.model.user.UserPaidAlbum;
+import com.atguigu.tingshu.model.user.UserPaidTrack;
 import com.atguigu.tingshu.user.mapper.*;
 import com.atguigu.tingshu.user.service.UserInfoService;
 import com.atguigu.tingshu.user.service.UserPaidTrackService;
+import com.atguigu.tingshu.user.strategy.PaidRecordStrategy;
+import com.atguigu.tingshu.user.strategy.factory.StrategyFactory;
 import com.atguigu.tingshu.vo.user.UserInfoVo;
 import com.atguigu.tingshu.vo.user.UserPaidRecordVo;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -30,7 +31,6 @@ import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
 
 import java.math.BigDecimal;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -249,6 +249,9 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
         return null;
     }
 
+    @Autowired
+    private StrategyFactory strategyFactory;
+
     /**
      * 虚拟物品发货,保存购买记录(声音、专辑、VIP会员)
      *
@@ -259,93 +262,10 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
     public void savePaidRecord(UserPaidRecordVo userPaidRecordVo) {
         //1.获取付款项目类型  付款项目类型: 1001-专辑 1002-声音 1003-vip会员
         String itemType = userPaidRecordVo.getItemType();
-        //2.处理付款项目类型为:声音 新增声音购买记录
-        if (SystemConstant.ORDER_ITEM_TYPE_TRACK.equals(itemType)) {
-            //2.1 判断订单编号是否已处理
-            Long count = userPaidTrackMapper.selectCount(
-                    new LambdaQueryWrapper<UserPaidTrack>()
-                            .eq(UserPaidTrack::getOrderNo, userPaidRecordVo.getOrderNo())
-            );
-            if (count > 0) {
-                return;
-            }
-            //2.2 未处理 构建声音购买记录(可能存在多条) 完成新增
-            List<Long> trackIdList = userPaidRecordVo.getItemIdList();
-            if (CollUtil.isNotEmpty(trackIdList)) {
-                //2.2.1 根据声音ID远程调用"专辑服务"查询声音信息
-                TrackInfo trackInfo = albumFeignClient.getTrackInfo(trackIdList.get(0)).getData();
-                //2.2.2 批量保存声音购买记录
-                List<UserPaidTrack> userPaidTrackList = trackIdList
-                        .stream()
-                        .map(trackId -> {
-                            UserPaidTrack userPaidTrack = new UserPaidTrack();
-                            userPaidTrack.setOrderNo(userPaidRecordVo.getOrderNo());
-                            userPaidTrack.setUserId(userPaidRecordVo.getUserId());
-                            userPaidTrack.setAlbumId(trackInfo.getAlbumId());
-                            userPaidTrack.setTrackId(trackId);
-                            return userPaidTrack;
-                        }).collect(Collectors.toList());
-                userPaidTrackService.saveBatch(userPaidTrackList);
-            }
-        } else if (SystemConstant.ORDER_ITEM_TYPE_ALBUM.equals(itemType)) {
-            //3.处理付款项目类型为:专辑 新增专辑购买记录
-            //3.1 判断订单编号是否已处理
-            Long count = userPaidAlbumMapper.selectCount(
-                    new LambdaQueryWrapper<UserPaidAlbum>().eq(UserPaidAlbum::getOrderNo, userPaidRecordVo.getOrderNo())
-            );
-            if (count > 0) {
-                return;
-            }
-            //3.2 创建专辑购买记录对象 新增
-            UserPaidAlbum userPaidAlbum = new UserPaidAlbum();
-            userPaidAlbum.setOrderNo(userPaidRecordVo.getOrderNo());
-            userPaidAlbum.setUserId(userPaidRecordVo.getUserId());
-            userPaidAlbum.setAlbumId(userPaidRecordVo.getItemIdList().get(0));
-            userPaidAlbumMapper.insert(userPaidAlbum);
-        } else if (SystemConstant.ORDER_ITEM_TYPE_VIP.equals(itemType)) {
-            //4.处理付款项目类型为:VIP套餐 新增套餐购买记录 更新 会员标识及过期时间
-            //4.1 判断订单编号是否已处理
-            Long count = userVipServiceMapper.selectCount(
-                    new LambdaQueryWrapper<UserVipService>()
-                            .eq(UserVipService::getOrderNo, userPaidRecordVo.getOrderNo())
-            );
-            if (count > 0) {
-                return;
-            }
-            //4.2 根据用户ID查询用户信息 判断是否为VIP
-            UserInfoVo userInfoVo = this.getUserInfo(userPaidRecordVo.getUserId());
-            Boolean isVIP = false;
-            if (userInfoVo.getIsVip().intValue() == 1 && userInfoVo.getVipExpireTime().after(new Date())) {
-                isVIP = true;
-            }
-
-            //4.3 根据套餐ID查询套餐信息 得到套餐服务月数
-            VipServiceConfig vipServiceConfig = vipServiceConfigMapper.selectById(userPaidRecordVo.getItemIdList().get(0));
-            Integer serviceMonth = vipServiceConfig.getServiceMonth();
-
-            //4.4 封装会员购买套餐记录并且保存 计算会员生效时间跟过期时间
-            UserVipService userVipService = new UserVipService();
-            //4.4.1 如果是新开会员,本次会员购买记录生效时间=当前系统时间,过期时间=当前系统时间+套餐服务月数
-            if (!isVIP) {
-                userVipService.setStartTime(new Date());
-                DateTime expireTime = DateUtil.offsetMonth(new Date(), serviceMonth);
-                userVipService.setExpireTime(expireTime);
-            } else {
-                //4.4.2 如果是续费会员,本次会员购买记录生效时间=现有会员过期时间+1天   过期时间:现有会员过期时间+1天+服务月数
-                userVipService.setStartTime(DateUtil.offsetDay(userInfoVo.getVipExpireTime(), 1));
-                userVipService.setExpireTime(DateUtil.offsetMonth(userVipService.getStartTime(), serviceMonth));
-            }
-            //4.4.3 设置其他属性
-            userVipService.setOrderNo(userPaidRecordVo.getOrderNo());
-            userVipService.setUserId(userPaidRecordVo.getUserId());
-            userVipServiceMapper.insert(userVipService);
 
-            //4.5 更新用户表中会员标识以及过期时间
-            userInfoVo.setIsVip(1);
-            userInfoVo.setVipExpireTime(userVipService.getExpireTime());
-            UserInfo userInfo = BeanUtil.copyProperties(userInfoVo, UserInfo.class);
-            baseMapper.updateById(userInfo);
-        }
+        //2.从策略工厂中获取付款项目类型对应的策略实现类对象
+        PaidRecordStrategy strategy = strategyFactory.getStrategy(itemType);
+        strategy.handlerPaidRecord(userPaidRecordVo);
     }
 
 }

+ 17 - 0
service/service-user/src/main/java/com/atguigu/tingshu/user/strategy/PaidRecordStrategy.java

@@ -0,0 +1,17 @@
+package com.atguigu.tingshu.user.strategy;
+
+import com.atguigu.tingshu.vo.user.UserPaidRecordVo;
+
+/**
+ * 处理不同类型商品购买记录的策略接口
+ */
+public interface PaidRecordStrategy {
+
+
+    /**
+     * 抽象出公用处理不同购买项目类型接口方法
+     * @param userPaidRecordVo
+     */
+    public void handlerPaidRecord(UserPaidRecordVo userPaidRecordVo);
+
+}

+ 52 - 0
service/service-user/src/main/java/com/atguigu/tingshu/user/strategy/factory/StrategyFactory.java

@@ -0,0 +1,52 @@
+package com.atguigu.tingshu.user.strategy.factory;
+
+import com.atguigu.tingshu.common.execption.GuiguException;
+import com.atguigu.tingshu.user.strategy.PaidRecordStrategy;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 策略工厂:管理所有策略实现类对象;对外提供根据商品类型返回策略实现对象方法
+ *
+ * @author: atguigu
+ * @create: 2025-03-25 09:14
+ */
+@Slf4j
+@Component
+public class StrategyFactory {
+
+
+    /**
+     * 自动注入方式为Map注入
+     * Map中key是Bean对象ID,自动从IOC容器中获取PaidRecordStrategy类型Bean,value是策略实现类Bean对象
+     */
+    @Autowired
+    private Map<String, PaidRecordStrategy> strategyMap;
+
+    /**
+     * 获取策略实现类对象注入到List
+     */
+    @Autowired
+    private List<PaidRecordStrategy> listStrategy;
+
+
+    /**
+     * 根据商品类型返回策略实现对象
+     * @param type
+     * @return
+     */
+    public PaidRecordStrategy getStrategy(String type) {
+        //根据商品类型返回对应的策略实现类对象
+        if (!strategyMap.containsKey(type)) {
+            log.error("[用户服务-策略工厂]未找到适配策略实现类:{}", type);
+            throw new GuiguException(500, "未找到适配策略实现类");
+        }
+        PaidRecordStrategy strategy = strategyMap.get(type);
+        log.info("[用户服务-策略工厂]根据商品类型返回对应的策略实现类对象:{}", strategy);
+        return strategy;
+    }
+}

+ 45 - 0
service/service-user/src/main/java/com/atguigu/tingshu/user/strategy/impl/AlbumPaidRecordStrategy.java

@@ -0,0 +1,45 @@
+package com.atguigu.tingshu.user.strategy.impl;
+
+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.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;
+
+/**
+ * @author: atguigu
+ * @create: 2025-03-25 09:08
+ */
+@Slf4j
+@Component("1001") //默认Bean对象ID=类名(首首字母小写)
+public class AlbumPaidRecordStrategy implements PaidRecordStrategy {
+
+    @Autowired
+    private UserPaidAlbumMapper userPaidAlbumMapper;
+
+    /**
+     * 处理专辑虚拟物品发货逻辑
+     * @param userPaidRecordVo
+     */
+    @Override
+    public void handlerPaidRecord(UserPaidRecordVo userPaidRecordVo) {
+        log.info("处理专辑虚拟物品发货逻辑");
+        //3.处理付款项目类型为:专辑 新增专辑购买记录
+        //3.1 判断订单编号是否已处理
+        Long count = userPaidAlbumMapper.selectCount(
+                new LambdaQueryWrapper<UserPaidAlbum>().eq(UserPaidAlbum::getOrderNo, userPaidRecordVo.getOrderNo())
+        );
+        if (count > 0) {
+            return;
+        }
+        //3.2 创建专辑购买记录对象 新增
+        UserPaidAlbum userPaidAlbum = new UserPaidAlbum();
+        userPaidAlbum.setOrderNo(userPaidRecordVo.getOrderNo());
+        userPaidAlbum.setUserId(userPaidRecordVo.getUserId());
+        userPaidAlbum.setAlbumId(userPaidRecordVo.getItemIdList().get(0));
+        userPaidAlbumMapper.insert(userPaidAlbum);
+    }
+}

+ 66 - 0
service/service-user/src/main/java/com/atguigu/tingshu/user/strategy/impl/TrackPaidRecordStrategy.java

@@ -0,0 +1,66 @@
+package com.atguigu.tingshu.user.strategy.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import com.atguigu.tingshu.album.AlbumFeignClient;
+import com.atguigu.tingshu.model.album.TrackInfo;
+import com.atguigu.tingshu.model.user.UserPaidTrack;
+import com.atguigu.tingshu.user.service.UserPaidTrackService;
+import com.atguigu.tingshu.user.strategy.PaidRecordStrategy;
+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 java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author: atguigu
+ * @create: 2025-03-25 09:08
+ */
+@Slf4j
+@Component("1002") //默认Bean对象ID=类名(首首字母小写)
+public class TrackPaidRecordStrategy implements PaidRecordStrategy {
+
+    @Autowired
+    private UserPaidTrackService userPaidTrackService;
+
+    @Autowired
+    private AlbumFeignClient albumFeignClient;
+
+    /**
+     * 处理声音虚拟物品发货逻辑
+     * @param userPaidRecordVo
+     */
+    @Override
+    public void handlerPaidRecord(UserPaidRecordVo userPaidRecordVo) {
+        log.info("处理声音虚拟物品发货逻辑");
+        //2.1 判断订单编号是否已处理
+        Long count = userPaidTrackService.count(
+                new LambdaQueryWrapper<UserPaidTrack>()
+                        .eq(UserPaidTrack::getOrderNo, userPaidRecordVo.getOrderNo())
+        );
+        if (count > 0) {
+            return;
+        }
+        //2.2 未处理 构建声音购买记录(可能存在多条) 完成新增
+        List<Long> trackIdList = userPaidRecordVo.getItemIdList();
+        if (CollUtil.isNotEmpty(trackIdList)) {
+            //2.2.1 根据声音ID远程调用"专辑服务"查询声音信息
+            TrackInfo trackInfo = albumFeignClient.getTrackInfo(trackIdList.get(0)).getData();
+            //2.2.2 批量保存声音购买记录
+            List<UserPaidTrack> userPaidTrackList = trackIdList
+                    .stream()
+                    .map(trackId -> {
+                        UserPaidTrack userPaidTrack = new UserPaidTrack();
+                        userPaidTrack.setOrderNo(userPaidRecordVo.getOrderNo());
+                        userPaidTrack.setUserId(userPaidRecordVo.getUserId());
+                        userPaidTrack.setAlbumId(trackInfo.getAlbumId());
+                        userPaidTrack.setTrackId(trackId);
+                        return userPaidTrack;
+                    }).collect(Collectors.toList());
+            userPaidTrackService.saveBatch(userPaidTrackList);
+        }
+    }
+}

+ 86 - 0
service/service-user/src/main/java/com/atguigu/tingshu/user/strategy/impl/VIPPaidRecordStrategy.java

@@ -0,0 +1,86 @@
+package com.atguigu.tingshu.user.strategy.impl;
+
+import cn.hutool.core.date.DateTime;
+import cn.hutool.core.date.DateUtil;
+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.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 java.util.Date;
+
+/**
+ * @author: atguigu
+ * @create: 2025-03-25 09:08
+ */
+@Slf4j
+@Component("1003") //默认Bean对象ID=类名(首首字母小写)
+public class VIPPaidRecordStrategy implements PaidRecordStrategy {
+
+    @Autowired
+    private UserVipServiceMapper userVipServiceMapper;
+
+    @Autowired
+    private UserInfoMapper userInfoMapper;
+
+    @Autowired
+    private VipServiceConfigMapper vipServiceConfigMapper;
+
+    /**
+     * 处理VIP虚拟物品发货逻辑
+     * @param userPaidRecordVo
+     */
+    @Override
+    public void handlerPaidRecord(UserPaidRecordVo userPaidRecordVo) {
+        log.info("处理VIP虚拟物品发货逻辑");
+        //4.处理付款项目类型为:VIP套餐 新增套餐购买记录 更新 会员标识及过期时间
+        //4.1 判断订单编号是否已处理
+        Long count = userVipServiceMapper.selectCount(
+                new LambdaQueryWrapper<UserVipService>()
+                        .eq(UserVipService::getOrderNo, userPaidRecordVo.getOrderNo())
+        );
+        if (count > 0) {
+            return;
+        }
+        //4.2 根据用户ID查询用户信息 判断是否为VIP
+        UserInfo userInfo = userInfoMapper.selectById(userPaidRecordVo.getUserId());
+        Boolean isVIP = false;
+        if (userInfo.getIsVip().intValue() == 1 && userInfo.getVipExpireTime().after(new Date())) {
+            isVIP = true;
+        }
+
+        //4.3 根据套餐ID查询套餐信息 得到套餐服务月数
+        VipServiceConfig vipServiceConfig = vipServiceConfigMapper.selectById(userPaidRecordVo.getItemIdList().get(0));
+        Integer serviceMonth = vipServiceConfig.getServiceMonth();
+
+        //4.4 封装会员购买套餐记录并且保存 计算会员生效时间跟过期时间
+        UserVipService userVipService = new UserVipService();
+        //4.4.1 如果是新开会员,本次会员购买记录生效时间=当前系统时间,过期时间=当前系统时间+套餐服务月数
+        if (!isVIP) {
+            userVipService.setStartTime(new Date());
+            DateTime expireTime = DateUtil.offsetMonth(new Date(), serviceMonth);
+            userVipService.setExpireTime(expireTime);
+        } else {
+            //4.4.2 如果是续费会员,本次会员购买记录生效时间=现有会员过期时间+1天   过期时间:现有会员过期时间+1天+服务月数
+            userVipService.setStartTime(DateUtil.offsetDay(userInfo.getVipExpireTime(), 1));
+            userVipService.setExpireTime(DateUtil.offsetMonth(userVipService.getStartTime(), serviceMonth));
+        }
+        //4.4.3 设置其他属性
+        userVipService.setOrderNo(userPaidRecordVo.getOrderNo());
+        userVipService.setUserId(userPaidRecordVo.getUserId());
+        userVipServiceMapper.insert(userVipService);
+
+        //4.5 更新用户表中会员标识以及过期时间
+        userInfo.setIsVip(1);
+        userInfo.setVipExpireTime(userVipService.getExpireTime());
+        userInfoMapper.updateById(userInfo);
+    }
+}

+ 24 - 0
service/service-user/src/main/java/com/atguigu/tingshu/user/strategy/impl/VIPProPaidRecordStrategy.java

@@ -0,0 +1,24 @@
+package com.atguigu.tingshu.user.strategy.impl;
+
+import com.atguigu.tingshu.user.strategy.PaidRecordStrategy;
+import com.atguigu.tingshu.vo.user.UserPaidRecordVo;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author: atguigu
+ * @create: 2025-03-25 09:08
+ */
+@Slf4j
+@Component("1004") //默认Bean对象ID=类名(首首字母小写)
+public class VIPProPaidRecordStrategy implements PaidRecordStrategy {
+
+    /**
+     * 处理VIPPRO虚拟物品发货逻辑
+     * @param userPaidRecordVo
+     */
+    @Override
+    public void handlerPaidRecord(UserPaidRecordVo userPaidRecordVo) {
+        log.info("处理VIP-PRO虚拟物品发货逻辑");
+    }
+}