it_lv 2 tygodni temu
rodzic
commit
d1802f31c4
1 zmienionych plików z 114 dodań i 101 usunięć
  1. 114 101
      第7章 订单.md

+ 114 - 101
第7章 订单.md

@@ -1197,15 +1197,13 @@ public OrderInfoVo trade(TradeVo tradeVo) {
 ```java
 /**
  * 扣减账户余额
- *
  * @param accountDeductVo
  * @return
  */
-@Operation(summary = "扣减账户余额")
+@Operation(summary = "检查扣减账户余额")
 @PostMapping("/userAccount/checkAndDeduct")
 public Result checkAndDeduct(@RequestBody AccountDeductVo accountDeductVo) {
-    //调用业务层完成余额扣减
-    userAccountService.checkAndDeduct(accountDeductVo);
+    userAccountService.deduct(accountDeductVo);
     return Result.ok();
 }
 ```
@@ -1214,31 +1212,41 @@ public Result checkAndDeduct(@RequestBody AccountDeductVo accountDeductVo) {
 
 ```java
 /**
- * 扣减账户余额
- *
+ * 检查扣减账户余额
  * @param accountDeductVo
- * @return
  */
-void checkAndDeduct(AccountDeductVo accountDeductVo);
+void deduct(AccountDeductVo accountDeductVo);
 ```
 
 业务实现类**UserAccountService**
 
 ```java
 /**
- * 扣减账户余额
+ * 检查扣减账户余额
+ * 避免账户余额超扣,采用分布式锁;数据库锁(悲观锁或乐观锁)
  *
  * @param accountDeductVo
- * @return
  */
 @Override
-public void checkAndDeduct(AccountDeductVo accountDeductVo) {
-    //1.扣减账户余额
-    int rows = userAccountMapper.checkAndDeduct(accountDeductVo);
-    if (rows == 0) {
-        throw new RuntimeException("账户余额不足");
+//@Transactional(rollbackFor = Exception.class) //TODO 引入Seata一定要去掉,否则会导致分布式事务失效
+public void deduct(AccountDeductVo accountDeductVo) {
+    //1.采用数据库悲观锁,检查账户余额是否满足扣款需求
+    BigDecimal amount = accountDeductVo.getAmount();
+    Long userId = accountDeductVo.getUserId();
+    UserAccount userAccount = userAccountMapper.checkDeduction(userId, amount);
+    if (userAccount == null) {
+        throw new GuiguException(500, "账户余额不足!");
     }
-    //2.新增账户变动日志
+    //2.扣减账户余额
+    userAccountMapper.update(
+            null,
+            new LambdaUpdateWrapper<UserAccount>()
+                    .eq(UserAccount::getUserId, userId)
+                    .setSql("total_amount = total_amount -" + amount)
+                    .setSql("available_amount = available_amount -" + amount)
+                    .setSql("total_pay_amount = total_pay_amount +" + amount)
+            );
+    //3.新增账户变动日志
     this.saveUserAccountDetail(accountDeductVo.getUserId(), accountDeductVo.getContent(), SystemConstant.ACCOUNT_TRADE_TYPE_MINUS, accountDeductVo.getAmount(), accountDeductVo.getOrderNo());
 }
 ```
@@ -1249,35 +1257,38 @@ public void checkAndDeduct(AccountDeductVo accountDeductVo) {
 package com.atguigu.tingshu.account.mapper;
 
 import com.atguigu.tingshu.model.account.UserAccount;
-import com.atguigu.tingshu.vo.account.AccountDeductVo;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 
+import java.math.BigDecimal;
+
 @Mapper
 public interface UserAccountMapper extends BaseMapper<UserAccount> {
 
     /**
-     * 扣减余额
-     * @param accountDeductVo
+     * 采用悲观锁,检查账户余额是否满足扣款需求
+     * @param userId
+     * @param amount
+     * @return
      */
-    int checkAndDeduct(@Param("vo") AccountDeductVo accountDeductVo);
+    UserAccount checkDeduction(@Param("userId") Long userId, @Param("amount") BigDecimal amount);
 }
 ```
 
 **UserAccountMapper.xml**
 
 ```xml
-<!--扣减余额-->
-<update id="checkAndDeduct">
-	UPDATE user_account
-	SET total_amount = total_amount - #{vo.amount},
-		available_amount = available_amount - #{vo.amount},
-		total_pay_amount = total_pay_amount + #{vo.amount}
+<!--采用悲观锁,检查账户余额是否满足扣款需求-->
+<select id="checkDeduction" resultType="com.atguigu.tingshu.model.account.UserAccount">
+	SELECT
+		*
+	FROM
+		user_account
 	WHERE
-		user_id = #{vo.userId}
-	  AND available_amount >= #{vo.amount}
-</update>
+		user_id = #{userId}
+	  AND available_amount >= #{amount} FOR UPDATE
+</select>
 ```
 
 在`service-account-client`模块中提供Feign接口:**AccountFeignClient**
@@ -1302,6 +1313,7 @@ import org.springframework.web.bind.annotation.RequestBody;
 @FeignClient(value = "service-account", path = "api/account", fallback = AccountDegradeFeignClient.class)
 public interface AccountFeignClient {
 
+
     /**
      * 扣减账户余额
      *
@@ -1310,9 +1322,7 @@ public interface AccountFeignClient {
      */
     @PostMapping("/userAccount/checkAndDeduct")
     public Result checkAndDeduct(@RequestBody AccountDeductVo accountDeductVo);
-
 }
-
 ```
 
 服务降级类:**AccountDegradeFeignClient**
@@ -1327,18 +1337,19 @@ import com.atguigu.tingshu.vo.account.AccountDeductVo;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
-@Component
 @Slf4j
+@Component
 public class AccountDegradeFeignClient implements AccountFeignClient {
 
     @Override
     public Result checkAndDeduct(AccountDeductVo accountDeductVo) {
+        log.error("【用户服务】提供checkAndDeduct远程调用失败");
         return null;
     }
 }
 ```
 
-### 2.3.2 新增购买记录
+### 2.3.2 虚拟物品发货
 
 > YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/118
 
@@ -1348,13 +1359,13 @@ public class AccountDegradeFeignClient implements AccountFeignClient {
 
 ```java
 /**
- * 用户付成功后虚拟物品(VIP,专辑,声音)发货
+ * 用户付成功后虚拟物品发货
  * @param userPaidRecordVo
  * @return
  */
-@Operation(summary = "用户付成功后虚拟物品发货")
+@Operation(summary = "用户付成功后虚拟物品发货")
 @PostMapping("/userInfo/savePaidRecord")
-public Result savePaidRecord(@RequestBody UserPaidRecordVo userPaidRecordVo){
+public Result savePaidRecord(@RequestBody UserPaidRecordVo userPaidRecordVo) {
     userInfoService.savePaidRecord(userPaidRecordVo);
     return Result.ok();
 }
@@ -1383,95 +1394,97 @@ private UserVipServiceMapper userVipServiceMapper;
 private VipServiceConfigMapper vipServiceConfigMapper;
 
 /**
- * 保存用户不同购买项目类型的购买记录
+ * 用户支付成功后,虚拟物品发货
  *
  * @param userPaidRecordVo
+ * @return
  */
 @Override
 public void savePaidRecord(UserPaidRecordVo userPaidRecordVo) {
-    //1001-专辑 1002-声音 1003-vip会员
+    //TODO 策略模式+工厂模式优化
+    //付款项目类型: 1001-专辑 1002-声音 1003-vip会员
     String itemType = userPaidRecordVo.getItemType();
-    //1.处理购买项目类型-声音
-    if (SystemConstant.ORDER_ITEM_TYPE_TRACK.equals(itemType)) {
-        //1.根据订单编号查询是否处理过声音购买记录
-        LambdaQueryWrapper<UserPaidTrack> userPaidTrackLambdaQueryWrapper = new LambdaQueryWrapper<>();
-        userPaidTrackLambdaQueryWrapper.eq(UserPaidTrack::getOrderNo, userPaidRecordVo.getOrderNo());
-        userPaidTrackLambdaQueryWrapper.select(UserPaidTrack::getId);
-        if (userPaidTrackMapper.selectCount(userPaidTrackLambdaQueryWrapper) > 0) {
+    //1.处理虚拟物品"专辑"发货逻辑
+    if (SystemConstant.ORDER_ITEM_TYPE_ALBUM.equals(itemType)) {
+        //1.1 根据订单编号查询已购专辑记录,如果存在,则返回
+        Long count = userPaidAlbumMapper.selectCount(
+                new LambdaQueryWrapper<UserPaidAlbum>()
+                        .eq(UserPaidAlbum::getOrderNo, userPaidRecordVo.getOrderNo())
+        );
+        if (count > 0) {
+            log.info("已存在该订单:{}的已购专辑记录", userPaidRecordVo.getOrderNo());
             return;
         }
-        //2.保存声音购买记录
-        //2.1 根据声音ID查询声音信息获取所属专辑ID
-        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);
-            }
-        }
-    } else if (SystemConstant.ORDER_ITEM_TYPE_ALBUM.equals(itemType)) {
-        //2.TODO 处理购买项目类型-专辑
-        //2.1 根据订单编号查询是否处理过专辑购买记录
-        LambdaQueryWrapper<UserPaidAlbum> paidAlbumLambdaQueryWrapper = new LambdaQueryWrapper<>();
-        paidAlbumLambdaQueryWrapper.eq(UserPaidAlbum::getOrderNo, userPaidRecordVo.getOrderNo());
-        if (userPaidAlbumMapper.selectCount(paidAlbumLambdaQueryWrapper) > 0) {
+        //1.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_TRACK.equals(itemType)) {
+        //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 保存专辑购买记录
-        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);
+        //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);
         }
     } else if (SystemConstant.ORDER_ITEM_TYPE_VIP.equals(itemType)) {
-        //3.处理购买项目类型-VIP会员
-        //3.1 根据订单编号查询是否处理过VIP购买记录
-        LambdaQueryWrapper<UserVipService> userVipServiceLambdaQueryWrapper = new LambdaQueryWrapper<>();
-        userVipServiceLambdaQueryWrapper.eq(UserVipService::getOrderNo, userPaidRecordVo.getOrderNo());
-        Long count = userVipServiceMapper.selectCount(userVipServiceLambdaQueryWrapper);
+        //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;
-        UserInfoVo userInfoVo = getUserInfo(userPaidRecordVo.getUserId());
-        if (userInfoVo.getIsVip().intValue() == 1 && userInfoVo.getVipExpireTime().after(new Date())) {
+        UserInfoVo userInfoVo   = this.getUserInfo(userPaidRecordVo.getUserId());
+        Date now = new Date();
+        if (userInfoVo.getIsVip().intValue() == 1 && userInfoVo.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(userInfoVo.getVipExpireTime(), 1);
-            userVipService.setStartTime(startTime);
-            userVipService.setExpireTime(DateUtil.offsetMonth(startTime, serviceMonth));
+            //会员用户,生效时间为=会员的到期时间+1 失效时间=会员的到期时间+购买会员套餐月数
+            Date vipExpireTime = userInfoVo.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标识及过期时间
-        UserInfo userInfo = new UserInfo();
-        userInfo.setId(userInfoVo.getId());
-        userInfo.setIsVip(1);
-        userInfo.setVipExpireTime(userVipService.getExpireTime());
+        //3.4 更新用户会员标识以及更新会员过期时间
+        userInfoVo.setIsVip(1);
+        userInfoVo.setVipExpireTime(userVipService.getExpireTime());
+        UserInfo userInfo = BeanUtil.copyProperties(userInfoVo, UserInfo.class);
         userInfoMapper.updateById(userInfo);
     }
 }