Browse Source

day13
订单结算-声音结算;订单提交,余额支付

it_lv 2 weeks ago
parent
commit
3e44d0b97f
22 changed files with 597 additions and 27 deletions
  1. 12 1
      service-client/service-account-client/src/main/java/com/atguigu/tingshu/account/AccountFeignClient.java
  2. 9 0
      service-client/service-account-client/src/main/java/com/atguigu/tingshu/account/impl/AccountDegradeFeignClient.java
  3. 13 4
      service-client/service-album-client/src/main/java/com/atguigu/tingshu/album/AlbumFeignClient.java
  4. 13 4
      service-client/service-album-client/src/main/java/com/atguigu/tingshu/album/impl/AlbumDegradeFeignClient.java
  5. 5 0
      service-client/service-user-client/src/main/java/com/atguigu/tingshu/user/client/UserFeignClient.java
  6. 7 0
      service-client/service-user-client/src/main/java/com/atguigu/tingshu/user/client/impl/UserDegradeFeignClient.java
  7. 16 3
      service/service-account/src/main/java/com/atguigu/tingshu/account/api/UserAccountApiController.java
  8. 8 0
      service/service-account/src/main/java/com/atguigu/tingshu/account/service/UserAccountService.java
  9. 26 0
      service/service-account/src/main/java/com/atguigu/tingshu/account/service/impl/UserAccountServiceImpl.java
  10. 15 0
      service/service-album/src/main/java/com/atguigu/tingshu/album/api/TrackInfoApiController.java
  11. 9 0
      service/service-album/src/main/java/com/atguigu/tingshu/album/service/TrackInfoService.java
  12. 34 0
      service/service-album/src/main/java/com/atguigu/tingshu/album/service/impl/TrackInfoServiceImpl.java
  13. 23 0
      service/service-order/src/main/java/com/atguigu/tingshu/order/api/OrderInfoApiController.java
  14. 1 0
      service/service-order/src/main/java/com/atguigu/tingshu/order/helper/SignHelper.java
  15. 7 0
      service/service-order/src/main/java/com/atguigu/tingshu/order/service/OrderDetailService.java
  16. 14 0
      service/service-order/src/main/java/com/atguigu/tingshu/order/service/OrderInfoService.java
  17. 17 0
      service/service-order/src/main/java/com/atguigu/tingshu/order/service/impl/OrderDetailServiceImpl.java
  18. 209 8
      service/service-order/src/main/java/com/atguigu/tingshu/order/service/impl/OrderInfoServiceImpl.java
  19. 13 0
      service/service-user/src/main/java/com/atguigu/tingshu/user/api/UserInfoApiController.java
  20. 9 0
      service/service-user/src/main/java/com/atguigu/tingshu/user/service/UserInfoService.java
  21. 121 7
      service/service-user/src/main/java/com/atguigu/tingshu/user/service/impl/UserInfoServiceImpl.java
  22. 16 0
      service/service-user/src/main/resources/application.yml

+ 12 - 1
service-client/service-account-client/src/main/java/com/atguigu/tingshu/account/AccountFeignClient.java

@@ -1,7 +1,11 @@
 package com.atguigu.tingshu.account;
 
 import com.atguigu.tingshu.account.impl.AccountDegradeFeignClient;
+import com.atguigu.tingshu.common.result.Result;
+import com.atguigu.tingshu.vo.account.AccountDeductVo;
 import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
 
 /**
  * <p>
@@ -10,7 +14,14 @@ import org.springframework.cloud.openfeign.FeignClient;
  *
  * @author atguigu
  */
-@FeignClient(value = "service-account", fallback = AccountDegradeFeignClient.class)
+@FeignClient(value = "service-account", path = "api/account", fallback = AccountDegradeFeignClient.class)
 public interface AccountFeignClient {
 
+    /**
+     * 检查扣减账户余额
+     * @param accountDeductVo
+     * @return
+     */
+    @PostMapping("/userAccount/checkAndDeduct")
+    public Result checkAndDeduct(@RequestBody AccountDeductVo accountDeductVo);
 }

+ 9 - 0
service-client/service-account-client/src/main/java/com/atguigu/tingshu/account/impl/AccountDegradeFeignClient.java

@@ -2,9 +2,18 @@ package com.atguigu.tingshu.account.impl;
 
 
 import com.atguigu.tingshu.account.AccountFeignClient;
+import com.atguigu.tingshu.common.result.Result;
+import com.atguigu.tingshu.vo.account.AccountDeductVo;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
+@Slf4j
 @Component
 public class AccountDegradeFeignClient implements AccountFeignClient {
 
+    @Override
+    public Result checkAndDeduct(AccountDeductVo accountDeductVo) {
+        log.error("[账户服务]提供远程调用方法:{},执行服务降级", "checkAndDeduct");
+        return null;
+    }
 }

+ 13 - 4
service-client/service-album-client/src/main/java/com/atguigu/tingshu/album/AlbumFeignClient.java

@@ -2,10 +2,7 @@ package com.atguigu.tingshu.album;
 
 import com.atguigu.tingshu.album.impl.AlbumDegradeFeignClient;
 import com.atguigu.tingshu.common.result.Result;
-import com.atguigu.tingshu.model.album.AlbumInfo;
-import com.atguigu.tingshu.model.album.BaseCategory1;
-import com.atguigu.tingshu.model.album.BaseCategory3;
-import com.atguigu.tingshu.model.album.BaseCategoryView;
+import com.atguigu.tingshu.model.album.*;
 import com.atguigu.tingshu.vo.album.AlbumStatVo;
 import org.springframework.cloud.openfeign.FeignClient;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -56,4 +53,16 @@ public interface AlbumFeignClient {
     @GetMapping("/albumInfo/getAlbumStatVo/{albumId}")
     public Result<AlbumStatVo> getAlbumStatVo(@PathVariable("albumId") Long albumId);
 
+    /**
+     * 获取待结算声音列表
+     * @param trackId
+     * @param trackCount
+     * @return
+     */
+    @GetMapping("/trackInfo/findPaidTrackInfoList/{trackId}/{trackCount}")
+    public Result<List<TrackInfo>> getWaitBuyTrackList(@PathVariable("trackId") Long trackId, @PathVariable("trackCount") Integer trackCount);
+
+    @GetMapping("/trackInfo/getTrackInfo/{id}")
+    public Result<TrackInfo> getTrackInfo(@PathVariable Long id);
+
 }

+ 13 - 4
service-client/service-album-client/src/main/java/com/atguigu/tingshu/album/impl/AlbumDegradeFeignClient.java

@@ -3,10 +3,7 @@ package com.atguigu.tingshu.album.impl;
 
 import com.atguigu.tingshu.album.AlbumFeignClient;
 import com.atguigu.tingshu.common.result.Result;
-import com.atguigu.tingshu.model.album.AlbumInfo;
-import com.atguigu.tingshu.model.album.BaseCategory1;
-import com.atguigu.tingshu.model.album.BaseCategory3;
-import com.atguigu.tingshu.model.album.BaseCategoryView;
+import com.atguigu.tingshu.model.album.*;
 import com.atguigu.tingshu.vo.album.AlbumStatVo;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
@@ -47,5 +44,17 @@ public class AlbumDegradeFeignClient implements AlbumFeignClient {
         return null;
     }
 
+    @Override
+    public Result<List<TrackInfo>> getWaitBuyTrackList(Long trackId, Integer trackCount) {
+        log.error("[专辑服务]提供远程{}调用执行服务降级", "findPaidTrackInfoList");
+        return null;
+    }
+
+    @Override
+    public Result<TrackInfo> getTrackInfo(Long id) {
+        log.error("[专辑服务]提供远程{}调用执行服务降级", "getTrackInfo");
+        return null;
+    }
+
 
 }

+ 5 - 0
service-client/service-user-client/src/main/java/com/atguigu/tingshu/user/client/UserFeignClient.java

@@ -4,6 +4,7 @@ import com.atguigu.tingshu.common.result.Result;
 import com.atguigu.tingshu.model.user.VipServiceConfig;
 import com.atguigu.tingshu.user.client.impl.UserDegradeFeignClient;
 import com.atguigu.tingshu.vo.user.UserInfoVo;
+import com.atguigu.tingshu.vo.user.UserPaidRecordVo;
 import org.springframework.cloud.openfeign.FeignClient;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -66,4 +67,8 @@ public interface UserFeignClient {
      */
     @GetMapping("/userInfo/findUserPaidTrackList/{albumId}")
     public Result<List<Long>> findUserPaidTrackList(@PathVariable("albumId") Long albumId);
+
+
+    @PostMapping("/userInfo/savePaidRecord")
+    public Result savePaidRecord(@RequestBody UserPaidRecordVo userPaidRecordVo);
 }

+ 7 - 0
service-client/service-user-client/src/main/java/com/atguigu/tingshu/user/client/impl/UserDegradeFeignClient.java

@@ -5,6 +5,7 @@ import com.atguigu.tingshu.common.result.Result;
 import com.atguigu.tingshu.model.user.VipServiceConfig;
 import com.atguigu.tingshu.user.client.UserFeignClient;
 import com.atguigu.tingshu.vo.user.UserInfoVo;
+import com.atguigu.tingshu.vo.user.UserPaidRecordVo;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
@@ -44,4 +45,10 @@ public class UserDegradeFeignClient implements UserFeignClient {
         log.error("[用户服务]提供远程{}调用执行服务降级", "findUserPaidTrackList");
         return null;
     }
+
+    @Override
+    public Result savePaidRecord(UserPaidRecordVo userPaidRecordVo) {
+        log.error("[用户服务]提供远程{}调用执行服务降级", "savePaidRecord");
+        return null;
+    }
 }

+ 16 - 3
service/service-account/src/main/java/com/atguigu/tingshu/account/api/UserAccountApiController.java

@@ -4,11 +4,11 @@ import com.atguigu.tingshu.account.service.UserAccountService;
 import com.atguigu.tingshu.common.login.GuiGuLogin;
 import com.atguigu.tingshu.common.result.Result;
 import com.atguigu.tingshu.common.util.AuthContextHolder;
+import com.atguigu.tingshu.vo.account.AccountDeductVo;
+import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 import java.math.BigDecimal;
 
@@ -34,5 +34,18 @@ public class UserAccountApiController {
 		BigDecimal availableAmount = userAccountService.getAvailableAmount(userId);
 		return Result.ok(availableAmount);
 	}
+
+
+	/**
+	 * 检查扣减账户余额
+	 * @param accountDeductVo
+	 * @return
+	 */
+	@Operation(summary = "检查扣减账户余额")
+	@PostMapping("/userAccount/checkAndDeduct")
+	public Result checkAndDeduct(@RequestBody AccountDeductVo accountDeductVo){
+		userAccountService.checkAndDeduct(accountDeductVo);
+		return Result.ok();
+	}
 }
 

+ 8 - 0
service/service-account/src/main/java/com/atguigu/tingshu/account/service/UserAccountService.java

@@ -1,6 +1,7 @@
 package com.atguigu.tingshu.account.service;
 
 import com.atguigu.tingshu.model.account.UserAccount;
+import com.atguigu.tingshu.vo.account.AccountDeductVo;
 import com.baomidou.mybatisplus.extension.service.IService;
 
 import java.math.BigDecimal;
@@ -33,4 +34,11 @@ public interface UserAccountService extends IService<UserAccount> {
      * @return
      */
     BigDecimal getAvailableAmount(Long userId);
+
+    /**
+     * 检查扣减账户余额
+     * @param accountDeductVo
+     * @return
+     */
+    void checkAndDeduct(AccountDeductVo accountDeductVo);
 }

+ 26 - 0
service/service-account/src/main/java/com/atguigu/tingshu/account/service/impl/UserAccountServiceImpl.java

@@ -5,9 +5,12 @@ import com.atguigu.tingshu.account.mapper.UserAccountDetailMapper;
 import com.atguigu.tingshu.account.mapper.UserAccountMapper;
 import com.atguigu.tingshu.account.service.UserAccountService;
 import com.atguigu.tingshu.common.constant.SystemConstant;
+import com.atguigu.tingshu.common.execption.GuiguException;
 import com.atguigu.tingshu.model.account.UserAccount;
 import com.atguigu.tingshu.model.account.UserAccountDetail;
+import com.atguigu.tingshu.vo.account.AccountDeductVo;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -87,4 +90,27 @@ public class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserA
         Assert.notNull(userAccount, "用户账户不存在");
         return userAccount.getAvailableAmount();
     }
+
+    /**
+     * 检查扣减账户余额
+     *
+     * @param accountDeductVo
+     * @return
+     */
+    @Override
+    public void checkAndDeduct(AccountDeductVo accountDeductVo) {
+        //1.扣减账户总金额、可用金额
+        LambdaUpdateWrapper<UserAccount> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.eq(UserAccount::getUserId, accountDeductVo.getUserId());
+        updateWrapper.ge(UserAccount::getAvailableAmount, accountDeductVo.getAmount());
+        updateWrapper.setSql("total_amount=total_amount - " + accountDeductVo.getAmount());
+        updateWrapper.setSql("available_amount=available_amount - " + accountDeductVo.getAmount());
+        updateWrapper.setSql("total_pay_amount=total_pay_amount + " + accountDeductVo.getAmount());
+        int update = baseMapper.update(null, updateWrapper);
+        if (update == 0) {
+            throw new GuiguException(500, "账户余额不足");
+        }
+        //2.记录账户变动日志
+        this.saveUserAccountDetail(accountDeductVo.getUserId(), accountDeductVo.getContent(), SystemConstant.ACCOUNT_TRADE_TYPE_MINUS, accountDeductVo.getAmount(), accountDeductVo.getOrderNo());
+    }
 }

+ 15 - 0
service/service-album/src/main/java/com/atguigu/tingshu/album/api/TrackInfoApiController.java

@@ -179,5 +179,20 @@ public class TrackInfoApiController {
         List<Map<String, Object>> list = trackInfoService.findUserTrackPaidList(userId, trackId);
         return Result.ok(list);
     }
+
+    /**
+     * 获取待结算声音列表
+     * @param trackId
+     * @param trackCount
+     * @return
+     */
+    @GuiGuLogin
+    @Operation(summary = "获取待结算声音列表")
+    @GetMapping("/trackInfo/findPaidTrackInfoList/{trackId}/{trackCount}")
+    public Result<List<TrackInfo>> getWaitBuyTrackList(@PathVariable("trackId") Long trackId, @PathVariable("trackCount") Integer trackCount){
+        Long userId = AuthContextHolder.getUserId();
+        List<TrackInfo> list = trackInfoService.findPaidTrackInfoList(userId, trackId, trackCount);
+        return Result.ok(list);
+    }
 }
 

+ 9 - 0
service/service-album/src/main/java/com/atguigu/tingshu/album/service/TrackInfoService.java

@@ -72,4 +72,13 @@ public interface TrackInfoService extends IService<TrackInfo> {
      * @return {data:[{name:"本集",price:0.2,trackCount:1},{name:"后10集",price:2,trackCount:10},{...},{name:"全集",price:4.4,trackCount:22}]}
      */
     List<Map<String, Object>> findUserTrackPaidList(Long userId, Long trackId);
+
+    /**
+     * 查询指定用户未购买声音列表
+     * @param userId 用户ID
+     * @param trackId 声音ID 作为起始声音
+     * @param trackCount 声音数量
+     * @return
+     */
+    List<TrackInfo> findPaidTrackInfoList(Long userId, Long trackId, Integer trackCount);
 }

+ 34 - 0
service/service-album/src/main/java/com/atguigu/tingshu/album/service/impl/TrackInfoServiceImpl.java

@@ -386,5 +386,39 @@ public class TrackInfoServiceImpl extends ServiceImpl<TrackInfoMapper, TrackInfo
         return list;
     }
 
+    /**
+     * 查询指定用户未购买声音列表
+     * @param userId 用户ID
+     * @param trackId 声音ID 作为起始声音
+     * @param trackCount 声音数量
+     * @return
+     */
+    @Override
+    public List<TrackInfo> findPaidTrackInfoList(Long userId, Long trackId, Integer trackCount) {
+        //1.根据选择起始声音ID查询声音记录 得到:声音序号、专辑ID
+        TrackInfo trackInfo = baseMapper.selectById(trackId);
+        Long albumId = trackInfo.getAlbumId();
+        Integer orderNum = trackInfo.getOrderNum();
+
+        //2.远程调用"用户服务"得到已购买声音ID列表
+        List<Long> userPaidTrackIdList = userFeignClient.findUserPaidTrackList(albumId).getData();
+
+        //3.根据声音序号(大于等于) 、专辑ID(等值)、声音ID(已购买排除) 按照序号升序、指定查询字段、返回购买数量声音列表
+        LambdaQueryWrapper<TrackInfo> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(TrackInfo::getAlbumId, albumId);
+        queryWrapper.ge(TrackInfo::getOrderNum, orderNum);
+        if(CollUtil.isNotEmpty(userPaidTrackIdList)){
+            queryWrapper.notIn(TrackInfo::getId, userPaidTrackIdList);
+        }
+        queryWrapper.orderByAsc(TrackInfo::getOrderNum);
+        //多查询一列专辑ID,为了获取声音价格
+        queryWrapper.select(TrackInfo::getId,TrackInfo::getTrackTitle,TrackInfo::getCoverUrl, TrackInfo::getAlbumId);
+        //按照用户选择购买数量返回
+        queryWrapper.last("limit "+trackCount);
+
+        //4.返回结果
+        return baseMapper.selectList(queryWrapper);
+    }
+
 
 }

+ 23 - 0
service/service-order/src/main/java/com/atguigu/tingshu/order/api/OrderInfoApiController.java

@@ -15,6 +15,9 @@ import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.util.HashMap;
+import java.util.Map;
+
 @Tag(name = "订单管理")
 @RestController
 @RequestMapping("api/order")
@@ -41,5 +44,25 @@ public class OrderInfoApiController {
 		return Result.ok(orderInfoVo);
 	}
 
+
+	/**
+	 * 提交订单
+	 * @param orderInfoVo
+	 * @return
+	 */
+	@GuiGuLogin
+	@Operation(summary = "提交订单")
+	@PostMapping("/orderInfo/submitOrder")
+	public Result<Map<String, String>> submitOrder(@RequestBody @Validated OrderInfoVo orderInfoVo) {
+		//1.获取用户ID
+		Long userId = AuthContextHolder.getUserId();
+		//2.调用业务逻辑处理订单提交
+		String orderNo = orderInfoService.submitOrder(userId, orderInfoVo);
+		//3.返回对象封装订单编号
+		Map<String, String> map = new HashMap<>();
+		map.put("orderNo", orderNo);
+		return Result.ok(map);
+	}
+
 }
 

+ 1 - 0
service/service-order/src/main/java/com/atguigu/tingshu/order/helper/SignHelper.java

@@ -33,6 +33,7 @@ public class SignHelper {
         //校验签名
         String signRemote = (String)parameterMap.get("sign");
 
+        //parameterMap包含旧签名
         String signLocal = getSign(parameterMap);
         if(StringUtils.isEmpty(signRemote)){
             throw new GuiguException(ResultCodeEnum.SIGN_ERROR);

+ 7 - 0
service/service-order/src/main/java/com/atguigu/tingshu/order/service/OrderDetailService.java

@@ -0,0 +1,7 @@
+package com.atguigu.tingshu.order.service;
+
+import com.atguigu.tingshu.model.order.OrderDetail;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+public interface OrderDetailService extends IService<OrderDetail> {
+}

+ 14 - 0
service/service-order/src/main/java/com/atguigu/tingshu/order/service/OrderInfoService.java

@@ -15,4 +15,18 @@ public interface OrderInfoService extends IService<OrderInfo> {
      * @return
      */
     OrderInfoVo trade(Long userId, TradeVo tradeVo);
+
+    /**
+     * 提交订单
+     * @param orderInfoVo
+     * @return 订单编号
+     */
+    String submitOrder(Long userId, OrderInfoVo orderInfoVo);
+
+    /**
+     * 保存订单及相关信息
+     * @param orderInfoVo
+     * @return
+     */
+    OrderInfo saveOrderInfo(OrderInfoVo orderInfoVo);
 }

+ 17 - 0
service/service-order/src/main/java/com/atguigu/tingshu/order/service/impl/OrderDetailServiceImpl.java

@@ -0,0 +1,17 @@
+package com.atguigu.tingshu.order.service.impl;
+
+import com.atguigu.tingshu.model.order.OrderDetail;
+import com.atguigu.tingshu.order.mapper.OrderDetailMapper;
+import com.atguigu.tingshu.order.service.OrderDetailService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author: atguigu
+ * @create: 2025-03-24 10:58
+ */
+@Slf4j
+@Service
+public class OrderDetailServiceImpl extends ServiceImpl<OrderDetailMapper, OrderDetail> implements OrderDetailService {
+}

+ 209 - 8
service/service-order/src/main/java/com/atguigu/tingshu/order/service/impl/OrderInfoServiceImpl.java

@@ -1,35 +1,49 @@
 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.service.OrderDetailService;
 import com.atguigu.tingshu.order.service.OrderInfoService;
 import com.atguigu.tingshu.user.client.UserFeignClient;
+import com.atguigu.tingshu.vo.account.AccountDeductVo;
 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 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.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 import static com.atguigu.tingshu.common.constant.SystemConstant.*;
 
@@ -50,6 +64,18 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
     @Autowired
     private RedisTemplate redisTemplate;
 
+    @Autowired
+    private OrderDetailMapper orderDetailMapper;
+
+    @Autowired
+    private OrderDetailService orderDetailService;
+
+    @Autowired
+    private OrderDerateMapper orderDerateMapper;
+
+    @Autowired
+    private AccountFeignClient accountFeignClient;
+
 
     /**
      * 对购买商品(VIP会员、专辑、声音)封装订单结算页所需要数据
@@ -149,17 +175,42 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
             orderDetailVoList.add(orderDetailVo);
 
             //3.6 封装订单优惠列表
-            if(originalAmount.compareTo(orderAmount) == 1){
+            if (originalAmount.compareTo(orderAmount) == 1) {
                 OrderDerateVo orderDerateVo = new OrderDerateVo();
                 orderDerateVo.setDerateType(ORDER_DERATE_ALBUM_DISCOUNT);
                 orderDerateVo.setDerateAmount(derateAmount);
-                orderDerateVo.setRemarks("专辑限时减免:"+derateAmount);
+                orderDerateVo.setRemarks("专辑限时减免:" + derateAmount);
                 orderDerateVoList.add(orderDerateVo);
             }
-        }
+        } else if (ORDER_ITEM_TYPE_TRACK.equals(itemType)) {
+            //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.TODO 处理购买商品类别:声音
+            //4.3 计算相关价格 声音不支持折扣
+            originalAmount = price.multiply(BigDecimal.valueOf(tradeVo.getTrackCount()));
+            orderAmount = originalAmount;
 
+            //4.4 封装订单明细列表
+            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());
+        }
 
         //5.封装订单VO 包含三类数据:价格相关、商品相关、其他杂项
         //5.1 为订单价、原价、减免价赋值
@@ -188,4 +239,154 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
         return orderInfoVo;
     }
 
+    /**
+     * 提交订单
+     *
+     * @param orderInfoVo
+     * @return {orderNo:订单编号}
+     */
+    @Override
+    public String submitOrder(Long userId, OrderInfoVo orderInfoVo) {
+        //1.业务校验-验证流水号 防止回退造成订单重复提交
+        //1.1 从Redis中获取当前用户存入流水号值
+        String tradeKey = RedisConstant.ORDER_TRADE_NO_PREFIX + userId;
+        //1.2 跟前端提交订单VO中流水号比较 采用LUA脚本确保判断跟删除原子性
+        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";
+        DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>(script, Boolean.class);
+        //1.2.1 如果流水号值一致则删除流水号
+        Boolean flag = (Boolean) redisTemplate.execute(redisScript, Arrays.asList(tradeKey), orderInfoVo.getTradeNo());
+        if (!flag) {
+            //1.2.2 反之(超时)抛出异常业务终止
+            throw new GuiguException(500, "流水号验证失败");
+        }
+
+        //2.业务校验-验证签名 防止用户篡改订单确认页订单相关信息
+        //2.1 将订单VO转为Map
+        Map<String, Object> map = BeanUtil.beanToMap(orderInfoVo);
+        //2.2 跟结算页加签内容一致 排除payWay字段
+        map.remove("payWay");
+        //2.3 调用验签方法
+        SignHelper.checkSign(map);
+
+        //业务校验通过
+        //3. 保存订单及订单明细跟优惠明细列表
+        OrderInfo orderInfo = this.saveOrderInfo(orderInfoVo);
+
+
+        String payWay = orderInfoVo.getPayWay();
+        //4. 处理付款方式为:余额支付,立即进行扣减余额;虚拟物品发货
+        if (ORDER_PAY_ACCOUNT.equals(payWay)) {
+            //4.1 远程调用"账户服务"扣减余额
+            //4.1.1 构建扣减余额VO对象
+            AccountDeductVo accountDeductVo = new AccountDeductVo();
+            accountDeductVo.setOrderNo(orderInfo.getOrderNo());
+            accountDeductVo.setUserId(orderInfo.getUserId());
+            accountDeductVo.setAmount(orderInfo.getOrderAmount());
+            accountDeductVo.setContent(orderInfo.getOrderTitle());
+            //4.1.2 远程调用完成扣减余额
+            Result result = accountFeignClient.checkAndDeduct(accountDeductVo);
+            //4.1.3 判断业务状态码是否为200
+            if (!result.getCode().equals(200)) {
+                throw new GuiguException(result.getCode(), result.getMessage());
+            }
+            //4.1.4 如果扣减余额成功,则保存订单状态为已支付
+            orderInfo.setOrderStatus(ORDER_STATUS_PAID);
+            baseMapper.updateById(orderInfo);
+
+            //4.2 TODO 远程调用"用户服务"虚拟物品发货
+            //4.2.1 构建虚拟物品发货VO对象
+            UserPaidRecordVo userPaidRecordVo = new UserPaidRecordVo();
+            userPaidRecordVo.setOrderNo(orderInfo.getOrderNo());
+            userPaidRecordVo.setUserId(orderInfo.getUserId());
+            userPaidRecordVo.setItemType(orderInfo.getItemType());
+            List<OrderDetailVo> orderDetailVoList = orderInfoVo.getOrderDetailVoList();
+            if (CollUtil.isNotEmpty(orderDetailVoList)) {
+                List<Long> itemIdList = orderDetailVoList.stream().map(OrderDetailVo::getItemId)
+                        .collect(Collectors.toList());
+                userPaidRecordVo.setItemIdList(itemIdList);
+                //4.2.2 远程调用"用户服务"虚拟物品发货
+                result = userFeignClient.savePaidRecord(userPaidRecordVo);
+                //4.2.3 判断远程调用相应结果业务状态码是否为200
+                if (!result.getCode().equals(200)) {
+                    throw new GuiguException(result.getCode(), result.getMessage());
+                }
+
+            }
+        }
+        if (ORDER_PAY_WAY_WEIXIN.equals(payWay)) {
+            //5. TODO 处理付款方式为:微信支付 采用延迟消息进行延迟关单
+
+        }
+
+        return orderInfo.getOrderNo();
+    }
+
+
+    /**
+     * 保存订单及相关信息
+     *
+     * @param orderInfoVo
+     * @return
+     */
+    @Override
+    public OrderInfo saveOrderInfo(OrderInfoVo orderInfoVo) {
+        //1.保存订单信息
+        //1.1 将订单VO拷贝到订单PO对象中
+        OrderInfo orderInfo = BeanUtil.copyProperties(orderInfoVo, OrderInfo.class);
+        //1.2 手动设置订单中属性
+        //1.2.1 设置用户ID
+        orderInfo.setUserId(AuthContextHolder.getUserId());
+        //1.2.2 设置订单状态:未支付 订单状态:0901-未支付 0902-已支付 0903-已取消"
+        orderInfo.setOrderStatus(ORDER_STATUS_UNPAID);
+        //1.2.3 设置订单购买内容
+        List<OrderDetailVo> orderDetailVoList = orderInfoVo.getOrderDetailVoList();
+        if (CollUtil.isNotEmpty(orderDetailVoList)) {
+            //String orderTitle = orderDetailVoList.stream()
+            //        .map(OrderDetailVo::getItemName)
+            //        .collect(Collectors.joining(","));
+            orderInfo.setOrderTitle(orderDetailVoList.get(0).getItemName());
+        }
+        //1.2.4 设置订单编号 普通订单编号规则=当日日期+分布式ID生成方案(雪花算法)
+        String orderNo = DateUtil.today().replaceAll("-", "") + IdWorker.getIdStr();
+        orderInfo.setOrderNo(orderNo);
+        //1.3 保存订单,得到订单ID
+        baseMapper.insert(orderInfo);
+        Long orderId = orderInfo.getId();
+
+        //2.保存订单商品明细列表
+        if (CollUtil.isNotEmpty(orderDetailVoList)) {
+            //2.1 方式一:调用持久层逐个保存
+            //    orderDetailVoList.stream()
+            //            .forEach(orderDetailVo -> {
+            //                OrderDetail orderDetail = BeanUtil.copyProperties(orderDetailVo, OrderDetail.class);
+            //                orderDetail.setOrderId(orderId);
+            //                orderDetailMapper.insert(orderDetail);
+            //            });
+            //2.1 方式二:调用业务层批量保存
+            List<OrderDetail> orderDetailList = orderDetailVoList.stream().map(orderDetailVo -> {
+                OrderDetail orderDetail = BeanUtil.copyProperties(orderDetailVo, OrderDetail.class);
+                orderDetail.setOrderId(orderId);
+                return orderDetail;
+            }).collect(Collectors.toList());
+            orderDetailService.saveBatch(orderDetailList);
+        }
+        //3.保存订单优惠明细列表
+        List<OrderDerateVo> orderDerateVoList = orderInfoVo.getOrderDerateVoList();
+        if (CollUtil.isNotEmpty(orderDetailVoList)) {
+            orderDerateVoList
+                    .stream()
+                    .forEach(orderDerateVo -> {
+                        OrderDerate orderDerate = BeanUtil.copyProperties(orderDerateVo, OrderDerate.class);
+                        orderDerate.setOrderId(orderId);
+                        orderDerateMapper.insert(orderDerate);
+                    });
+        }
+        return orderInfo;
+    }
+
 }

+ 13 - 0
service/service-user/src/main/java/com/atguigu/tingshu/user/api/UserInfoApiController.java

@@ -6,6 +6,7 @@ import com.atguigu.tingshu.common.result.Result;
 import com.atguigu.tingshu.common.util.AuthContextHolder;
 import com.atguigu.tingshu.user.service.UserInfoService;
 import com.atguigu.tingshu.vo.user.UserInfoVo;
+import com.atguigu.tingshu.vo.user.UserPaidRecordVo;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -84,5 +85,17 @@ public class UserInfoApiController {
         return Result.ok(list);
     }
 
+
+    /**
+     * 用户付款成功后,虚拟物品发货
+     * @param userPaidRecordVo
+     * @return
+     */
+    @Operation(summary = "用户付款成功后,虚拟物品发货")
+    @PostMapping("/userInfo/savePaidRecord")
+    public Result savePaidRecord(@RequestBody UserPaidRecordVo userPaidRecordVo){
+        userInfoService.savePaidRecord(userPaidRecordVo);
+        return Result.ok();
+    }
 }
 

+ 9 - 0
service/service-user/src/main/java/com/atguigu/tingshu/user/service/UserInfoService.java

@@ -2,6 +2,7 @@ package com.atguigu.tingshu.user.service;
 
 import com.atguigu.tingshu.model.user.UserInfo;
 import com.atguigu.tingshu.vo.user.UserInfoVo;
+import com.atguigu.tingshu.vo.user.UserPaidRecordVo;
 import com.baomidou.mybatisplus.extension.service.IService;
 
 import java.util.List;
@@ -55,4 +56,12 @@ public interface UserInfoService extends IService<UserInfo> {
      * @return
      */
     List<Long> findUserPaidTrackList(Long userId, Long albumId);
+
+
+    /**
+     * 虚拟物品发货,保存购买记录(声音、专辑、VIP会员)
+     * @param userPaidRecordVo
+     * @return
+     */
+    void savePaidRecord(UserPaidRecordVo userPaidRecordVo);
 }

+ 121 - 7
service/service-user/src/main/java/com/atguigu/tingshu/user/service/impl/UserInfoServiceImpl.java

@@ -5,20 +5,22 @@ 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.user.UserInfo;
-import com.atguigu.tingshu.model.user.UserPaidAlbum;
-import com.atguigu.tingshu.model.user.UserPaidTrack;
-import com.atguigu.tingshu.user.mapper.UserInfoMapper;
-import com.atguigu.tingshu.user.mapper.UserPaidAlbumMapper;
-import com.atguigu.tingshu.user.mapper.UserPaidTrackMapper;
+import com.atguigu.tingshu.model.album.TrackInfo;
+import com.atguigu.tingshu.model.user.*;
+import com.atguigu.tingshu.user.mapper.*;
 import com.atguigu.tingshu.user.service.UserInfoService;
+import com.atguigu.tingshu.user.service.UserPaidTrackService;
 import com.atguigu.tingshu.vo.user.UserInfoVo;
+import com.atguigu.tingshu.vo.user.UserPaidRecordVo;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import lombok.extern.slf4j.Slf4j;
@@ -28,6 +30,7 @@ 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;
@@ -57,6 +60,19 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
     @Autowired
     private UserPaidTrackMapper userPaidTrackMapper;
 
+    @Autowired
+    private UserPaidTrackService userPaidTrackService;
+
+    @Autowired
+    private AlbumFeignClient albumFeignClient;
+
+
+    @Autowired
+    private UserVipServiceMapper userVipServiceMapper;
+
+    @Autowired
+    private VipServiceConfigMapper vipServiceConfigMapper;
+
     /**
      * 微信登录
      *
@@ -224,7 +240,7 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
         queryWrapper.eq(UserPaidTrack::getUserId, userId);
         queryWrapper.select(UserPaidTrack::getTrackId);
         List<UserPaidTrack> userPaidTrackList = userPaidTrackMapper.selectList(queryWrapper);
-        if(CollUtil.isNotEmpty(userPaidTrackList)){
+        if (CollUtil.isNotEmpty(userPaidTrackList)) {
             return userPaidTrackList
                     .stream()
                     .map(UserPaidTrack::getTrackId)
@@ -233,5 +249,103 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
         return null;
     }
 
+    /**
+     * 虚拟物品发货,保存购买记录(声音、专辑、VIP会员)
+     *
+     * @param userPaidRecordVo
+     * @return
+     */
+    @Override
+    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);
+        }
+    }
 
 }

+ 16 - 0
service/service-user/src/main/resources/application.yml

@@ -0,0 +1,16 @@
+############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
+sa-token:
+  # token 名称(同时也是 cookie 名称)
+  token-name: token
+  # token 有效期(单位:秒) 默认30天,-1 代表永久有效
+  timeout: 2592000
+  # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
+  active-timeout: -1
+  # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
+  is-concurrent: true
+  # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
+  is-share: false
+  # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
+  token-style: uuid
+  # 是否输出操作日志
+  is-log: true