Эх сурвалжийг харах

day12
订单结算-会员;专辑;声音(查询分集购买列表)

it_lv 1 долоо хоног өмнө
parent
commit
91d852f1bf
17 өөрчлөгдсөн 588 нэмэгдсэн , 56 устгасан
  1. 7 2
      common/service-util/src/main/java/com/atguigu/tingshu/common/handler/GlobalExceptionHandler.java
  2. 27 0
      service-client/service-user-client/src/main/java/com/atguigu/tingshu/user/client/UserFeignClient.java
  3. 19 0
      service-client/service-user-client/src/main/java/com/atguigu/tingshu/user/client/impl/UserDegradeFeignClient.java
  4. 19 0
      service/service-account/src/main/java/com/atguigu/tingshu/account/api/UserAccountApiController.java
  5. 7 0
      service/service-account/src/main/java/com/atguigu/tingshu/account/service/UserAccountService.java
  6. 65 46
      service/service-account/src/main/java/com/atguigu/tingshu/account/service/impl/UserAccountServiceImpl.java
  7. 18 0
      service/service-album/src/main/java/com/atguigu/tingshu/album/api/TrackInfoApiController.java
  8. 10 0
      service/service-album/src/main/java/com/atguigu/tingshu/album/service/TrackInfoService.java
  9. 62 5
      service/service-album/src/main/java/com/atguigu/tingshu/album/service/impl/TrackInfoServiceImpl.java
  10. 36 0
      service/service-cdc/src/main/java/com/atguigu/tingshu/listener/AlbumListener.java
  11. 25 0
      service/service-order/src/main/java/com/atguigu/tingshu/order/api/OrderInfoApiController.java
  12. 9 0
      service/service-order/src/main/java/com/atguigu/tingshu/order/service/OrderInfoService.java
  13. 171 0
      service/service-order/src/main/java/com/atguigu/tingshu/order/service/impl/OrderInfoServiceImpl.java
  14. 32 0
      service/service-user/src/main/java/com/atguigu/tingshu/user/api/UserInfoApiController.java
  15. 23 0
      service/service-user/src/main/java/com/atguigu/tingshu/user/api/VipServiceConfigApiController.java
  16. 16 0
      service/service-user/src/main/java/com/atguigu/tingshu/user/service/UserInfoService.java
  17. 42 3
      service/service-user/src/main/java/com/atguigu/tingshu/user/service/impl/UserInfoServiceImpl.java

+ 7 - 2
common/service-util/src/main/java/com/atguigu/tingshu/common/handler/GlobalExceptionHandler.java

@@ -11,7 +11,6 @@ import org.springframework.web.bind.MethodArgumentNotValidException;
 import org.springframework.web.bind.annotation.ControllerAdvice;
 import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.ResponseBody;
-import org.springframework.web.bind.annotation.RestControllerAdvice;
 
 import java.util.HashMap;
 import java.util.List;
@@ -48,7 +47,6 @@ public class GlobalExceptionHandler {
     }
 
 
-
     @ExceptionHandler({IllegalArgumentException.class})
     @ResponseBody
     public Result llegalArgumentException(Exception e) {
@@ -56,6 +54,13 @@ public class GlobalExceptionHandler {
         return Result.build(null, ResultCodeEnum.ARGUMENT_VALID_ERROR);
     }
 
+    @ExceptionHandler({IllegalStateException.class})
+    @ResponseBody
+    public Result IllegalStateException(IllegalStateException e) {
+        log.error("触发异常拦截: " + e.getMessage(), e);
+        return Result.build(null, 500, e.getMessage());
+    }
+
 
     @ExceptionHandler(value = BindException.class)
     @ResponseBody

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

@@ -1,6 +1,7 @@
 package com.atguigu.tingshu.user.client;
 
 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 org.springframework.cloud.openfeign.FeignClient;
@@ -39,4 +40,30 @@ public interface UserFeignClient {
             @PathVariable("albumId") Long albumId,
             @RequestBody List<Long> needCheckBuyStateTrackIds
     );
+
+
+    /**
+     * 根据套餐ID查询套餐详情
+     * @param id
+     * @return
+     */
+    @GetMapping("/vipServiceConfig/getVipServiceConfig/{id}")
+    public Result<VipServiceConfig> getVipServiceConfig(@PathVariable("id") Long id);
+
+
+    /**
+     * 验证当前用户是否购买过指定专辑
+     * @param albumId
+     * @return
+     */
+    @GetMapping("/userInfo/isPaidAlbum/{albumId}")
+    public Result<Boolean> isPaidAlbum(@PathVariable("albumId") Long albumId);
+
+    /**
+     * 查询用户指定专辑已购声音ID列表
+     * @param albumId
+     * @return
+     */
+    @GetMapping("/userInfo/findUserPaidTrackList/{albumId}")
+    public Result<List<Long>> findUserPaidTrackList(@PathVariable("albumId") Long albumId);
 }

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

@@ -2,6 +2,7 @@ package com.atguigu.tingshu.user.client.impl;
 
 
 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 lombok.extern.slf4j.Slf4j;
@@ -25,4 +26,22 @@ public class UserDegradeFeignClient implements UserFeignClient {
         log.error("[用户服务]提供远程{}调用执行服务降级", "userIsPaidTrack");
         return null;
     }
+
+    @Override
+    public Result<VipServiceConfig> getVipServiceConfig(Long id) {
+        log.error("[用户服务]提供远程{}调用执行服务降级", "getVipServiceConfig");
+        return null;
+    }
+
+    @Override
+    public Result<Boolean> isPaidAlbum(Long albumId) {
+        log.error("[用户服务]提供远程{}调用执行服务降级", "isPaidAlbum");
+        return null;
+    }
+
+    @Override
+    public Result<List<Long>> findUserPaidTrackList(Long albumId) {
+        log.error("[用户服务]提供远程{}调用执行服务降级", "findUserPaidTrackList");
+        return null;
+    }
 }

+ 19 - 0
service/service-account/src/main/java/com/atguigu/tingshu/account/api/UserAccountApiController.java

@@ -1,11 +1,17 @@
 package com.atguigu.tingshu.account.api;
 
 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 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 java.math.BigDecimal;
+
 @Tag(name = "用户账户管理")
 @RestController
 @RequestMapping("api/account")
@@ -15,5 +21,18 @@ public class UserAccountApiController {
 	@Autowired
 	private UserAccountService userAccountService;
 
+	/**
+	 * 获取用户账户可用余额
+	 * @return
+	 */
+	@GuiGuLogin
+	@GetMapping("/userAccount/getAvailableAmount")
+	public Result<BigDecimal> getAvailableAmount() {
+		//1.获取当前用户ID
+		Long userId = AuthContextHolder.getUserId();
+		//2.获取指定用户账户可用余额
+		BigDecimal availableAmount = userAccountService.getAvailableAmount(userId);
+		return Result.ok(availableAmount);
+	}
 }
 

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

@@ -26,4 +26,11 @@ public interface UserAccountService extends IService<UserAccount> {
      * @param orderNo 订单编号
      */
     void saveUserAccountDetail(Long userId, String title, String tradeType, BigDecimal amount, String orderNo);
+
+    /**
+     * 获取指定用户账户可用余额
+     * @param userId
+     * @return
+     */
+    BigDecimal getAvailableAmount(Long userId);
 }

+ 65 - 46
service/service-account/src/main/java/com/atguigu/tingshu/account/service/impl/UserAccountServiceImpl.java

@@ -1,11 +1,13 @@
 package com.atguigu.tingshu.account.service.impl;
 
+import cn.hutool.core.lang.Assert;
 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.model.account.UserAccount;
 import com.atguigu.tingshu.model.account.UserAccountDetail;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -20,52 +22,69 @@ import java.util.Map;
 @SuppressWarnings({"all"})
 public class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserAccount> implements UserAccountService {
 
-	@Autowired
-	private UserAccountMapper userAccountMapper;
+    @Autowired
+    private UserAccountMapper userAccountMapper;
 
-	@Autowired
-	private UserAccountDetailMapper userAccountDetailMapper;
-	/**
-	 * 初始化账户记录
-	 *
-	 * @param map
-	 */
-	@Override
-	@Transactional(rollbackFor = Exception.class)
-	public void saveUserAccount(Map<String, Object> map) {
-		Long userId = (Long) map.get("userId");
-		BigDecimal amount = (BigDecimal) map.get("amount");
-		String title = (String) map.get("title");
-		String orderNo = (String) map.get("orderNo");
-		//1.构建保存用户账户对象
-		UserAccount userAccount = new UserAccount();
-		userAccount.setUserId(userId);
-		userAccount.setTotalAmount(amount);
-		userAccount.setLockAmount(BigDecimal.valueOf(0));
-		userAccount.setAvailableAmount(amount);
-		userAccount.setTotalIncomeAmount(amount);
-		userAccount.setTotalPayAmount(BigDecimal.valueOf(0));
-		baseMapper.insert(userAccount);
-		//2.保存用户账户变动日志记录
-		this.saveUserAccountDetail(userId,title, SystemConstant.ACCOUNT_TRADE_TYPE_DEPOSIT, amount, orderNo);
-	}
+    @Autowired
+    private UserAccountDetailMapper userAccountDetailMapper;
 
-	/**
-	 * 新增账户变动日志
-	 * @param userId 用户ID
-	 * @param title 内容
-	 * @param tradeType 交易类型:1201-充值 1202-锁定 1203-解锁 1204-消费
-	 * @param amount 金额
-	 * @param orderNo 订单编号
-	 */
-	@Override
-	public void saveUserAccountDetail(Long userId, String title, String tradeType, BigDecimal amount, String orderNo) {
-		UserAccountDetail userAccountDetail = new UserAccountDetail();
-		userAccountDetail.setUserId(userId);
-		userAccountDetail.setTitle(title);
-		userAccountDetail.setTradeType(tradeType);
-		userAccountDetail.setAmount(amount);
-		userAccountDetail.setOrderNo(orderNo);
-		userAccountDetailMapper.insert(userAccountDetail);
-	}
+    /**
+     * 初始化账户记录
+     *
+     * @param map
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void saveUserAccount(Map<String, Object> map) {
+        Long userId = (Long) map.get("userId");
+        BigDecimal amount = (BigDecimal) map.get("amount");
+        String title = (String) map.get("title");
+        String orderNo = (String) map.get("orderNo");
+        //1.构建保存用户账户对象
+        UserAccount userAccount = new UserAccount();
+        userAccount.setUserId(userId);
+        userAccount.setTotalAmount(amount);
+        userAccount.setLockAmount(BigDecimal.valueOf(0));
+        userAccount.setAvailableAmount(amount);
+        userAccount.setTotalIncomeAmount(amount);
+        userAccount.setTotalPayAmount(BigDecimal.valueOf(0));
+        baseMapper.insert(userAccount);
+        //2.保存用户账户变动日志记录
+        this.saveUserAccountDetail(userId, title, SystemConstant.ACCOUNT_TRADE_TYPE_DEPOSIT, amount, orderNo);
+    }
+
+    /**
+     * 新增账户变动日志
+     *
+     * @param userId    用户ID
+     * @param title     内容
+     * @param tradeType 交易类型:1201-充值 1202-锁定 1203-解锁 1204-消费
+     * @param amount    金额
+     * @param orderNo   订单编号
+     */
+    @Override
+    public void saveUserAccountDetail(Long userId, String title, String tradeType, BigDecimal amount, String orderNo) {
+        UserAccountDetail userAccountDetail = new UserAccountDetail();
+        userAccountDetail.setUserId(userId);
+        userAccountDetail.setTitle(title);
+        userAccountDetail.setTradeType(tradeType);
+        userAccountDetail.setAmount(amount);
+        userAccountDetail.setOrderNo(orderNo);
+        userAccountDetailMapper.insert(userAccountDetail);
+    }
+
+    /**
+     * 获取指定用户账户可用余额
+     *
+     * @param userId
+     * @return
+     */
+    @Override
+    public BigDecimal getAvailableAmount(Long userId) {
+        LambdaQueryWrapper<UserAccount> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(UserAccount::getUserId, userId);
+        UserAccount userAccount = baseMapper.selectOne(queryWrapper);
+        Assert.notNull(userAccount, "用户账户不存在");
+        return userAccount.getAvailableAmount();
+    }
 }

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

@@ -19,6 +19,7 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
+import java.util.List;
 import java.util.Map;
 
 @Tag(name = "声音管理")
@@ -161,5 +162,22 @@ public class TrackInfoApiController {
         TrackStatVo trackStatVo = trackInfoService.getTrackStatVo(trackId);
         return Result.ok(trackStatVo);
     }
+
+
+    /**
+     * 基于用户未购买声音数量动态构建分集购买列表
+     * @param trackId 选择购买(起始位置)声音ID
+     * @return {data:[{name:"本集",price:0.2,trackCount:1},{name:"后10集",price:2,trackCount:10},{...},{name:"全集",price:4.4,trackCount:22}]}
+     */
+    @GuiGuLogin
+    @Operation(summary = "基于用户未购买声音数量动态构建分集购买列表")
+    @GetMapping("/trackInfo/findUserTrackPaidList/{trackId}")
+    public Result<List<Map<String, Object>>> findUserTrackPaidList(@PathVariable Long trackId){
+        //1.获取当前用户ID
+        Long userId = AuthContextHolder.getUserId();
+        //2.调用业务层方法封装分集购买列表
+        List<Map<String, Object>> list = trackInfoService.findUserTrackPaidList(userId, trackId);
+        return Result.ok(list);
+    }
 }
 

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

@@ -6,6 +6,9 @@ import com.atguigu.tingshu.vo.album.*;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.IService;
 
+import java.util.List;
+import java.util.Map;
+
 public interface TrackInfoService extends IService<TrackInfo> {
 
     /**
@@ -62,4 +65,11 @@ public interface TrackInfoService extends IService<TrackInfo> {
      * @param mqVo
      */
     void updateTrackStat(TrackStatMqVo mqVo);
+
+    /**
+     * 基于用户未购买声音数量动态构建分集购买列表
+     * @param trackId 选择购买(起始位置)声音ID
+     * @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);
 }

+ 62 - 5
service/service-album/src/main/java/com/atguigu/tingshu/album/service/impl/TrackInfoServiceImpl.java

@@ -28,9 +28,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.stream.Collectors;
 
 import static com.atguigu.tingshu.common.constant.SystemConstant.ALBUM_PAY_TYPE_REQUIRE;
@@ -309,7 +307,7 @@ public class TrackInfoServiceImpl extends ServiceImpl<TrackInfoMapper, TrackInfo
                         .setSql("stat_num = stat_num + " + mqVo.getCount())
         );
         //2.如果统计类型是:播放量、评论量 同时更新所属专辑统计数值
-        if(SystemConstant.TRACK_STAT_PLAY.equals(mqVo.getStatType())){
+        if (SystemConstant.TRACK_STAT_PLAY.equals(mqVo.getStatType())) {
             albumStatMapper.update(
                     null,
                     new LambdaUpdateWrapper<AlbumStat>()
@@ -318,7 +316,7 @@ public class TrackInfoServiceImpl extends ServiceImpl<TrackInfoMapper, TrackInfo
                             .setSql("stat_num = stat_num + " + mqVo.getCount())
             );
         }
-        if(SystemConstant.TRACK_STAT_COMMENT.equals(mqVo.getStatType())){
+        if (SystemConstant.TRACK_STAT_COMMENT.equals(mqVo.getStatType())) {
             albumStatMapper.update(
                     null,
                     new LambdaUpdateWrapper<AlbumStat>()
@@ -329,5 +327,64 @@ public class TrackInfoServiceImpl extends ServiceImpl<TrackInfoMapper, TrackInfo
         }
     }
 
+    /**
+     * 基于用户未购买声音数量动态构建分集购买列表
+     *
+     * @param trackId 选择购买(起始位置)声音ID
+     * @return {data:[{name:"本集",price:0.2,trackCount:1},{name:"后10集",price:2,trackCount:10},{...},{name:"全集",price:4.4,trackCount:22}]}
+     */
+    @Override
+    public List<Map<String, Object>> findUserTrackPaidList(Long userId, Long trackId) {
+        //1.根据选择起始声音ID查询声音记录 得到:声音序号、专辑ID
+        TrackInfo trackInfo = baseMapper.selectById(trackId);
+        Integer orderNum = trackInfo.getOrderNum();
+        Long albumId = trackInfo.getAlbumId();
+        //2.根据声音序号 专辑ID 按照序号升序 得到"待购买"声音列表(可能包含用户已购买)
+        List<TrackInfo> waitBuyTrackInfoList = baseMapper.selectList(
+                new LambdaQueryWrapper<TrackInfo>()
+                        .eq(TrackInfo::getAlbumId, albumId)
+                        .ge(TrackInfo::getOrderNum, orderNum)
+                        .orderByAsc(TrackInfo::getOrderNum)
+        );
+
+        //3.远程调用"用户服务"查询该专辑已购声音ID列表
+        List<Long> userPaidTrackIdList = userFeignClient.findUserPaidTrackList(albumId).getData();
+        if (CollUtil.isNotEmpty(userPaidTrackIdList)) {
+            //4.处理"待购买"声音列表,过滤掉用户已购声音ID
+            waitBuyTrackInfoList = waitBuyTrackInfoList
+                    .stream()
+                    .filter(trackInfo1 -> !userPaidTrackIdList.contains(trackInfo1.getId()))
+                    .collect(Collectors.toList());
+        }
+        //5.得到"最终待购买"声音列表,根据列表长度动态构建分集购买列表(最多显示后50级)
+        int size = waitBuyTrackInfoList.size();
+        List<Map<String, Object>> list = new ArrayList<>();
+        //5.1 构建本集分集购买对象
+        AlbumInfo albumInfo = albumInfoMapper.selectById(albumId);
+        BigDecimal price = albumInfo.getPrice();
+        Map<String, Object> curr = new HashMap<>();
+        curr.put("name", "本集");
+        curr.put("price", price);
+        curr.put("trackCount", 1);
+        list.add(curr);
+        //5.2 构建其他分集购买对象 后10集 后20集 ... 全集   未购买数量:35     25
+        for (int i = 10; i <= 50; i += 10) {
+            Map<String, Object> map = new HashMap<>();
+            if (size > i) {
+                map.put("name", "后" + i + "集");
+                map.put("price", price.multiply(BigDecimal.valueOf(i)));
+                map.put("trackCount", i);
+                list.add(map);
+            } else {
+                map.put("name", "全集(后" + size + "集)");
+                map.put("price", price.multiply(BigDecimal.valueOf(size)));
+                map.put("trackCount", size);
+                list.add(map);
+                break;
+            }
+        }
+        return list;
+    }
+
 
 }

+ 36 - 0
service/service-cdc/src/main/java/com/atguigu/tingshu/listener/AlbumListener.java

@@ -0,0 +1,36 @@
+package com.atguigu.tingshu.listener;
+
+import com.atguigu.tingshu.model.album.AlbumInfo;
+import io.xzxj.canal.core.annotation.CanalListener;
+import io.xzxj.canal.core.listener.EntryListener;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+
+import java.util.Set;
+
+/**
+ * @author: atguigu
+ * @create: 2025-03-22 08:46
+ */
+@Slf4j
+@CanalListener(destination = "tingshuTopic", schemaName = "tingshu_album", tableName = "album_info")
+public class AlbumListener implements EntryListener<AlbumInfo> {
+
+    @Autowired
+    private RedisTemplate redisTemplate;
+
+    /**
+     * 监听到转机信息表更新
+     * @param before 变更前数据
+     * @param after 变更后的数据
+     * @param fields 变更字段
+     */
+    @Override
+    public void update(AlbumInfo before, AlbumInfo after, Set<String> fields) {
+        log.info("[cdc]监听到变更数据,变更前:{},变更后:{}", before, after);
+        Long id = after.getId();
+        String redisKey = "album:info:"+id;
+        redisTemplate.delete(redisKey);
+    }
+}

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

@@ -1,8 +1,17 @@
 package com.atguigu.tingshu.order.api;
 
+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.order.service.OrderInfoService;
+import com.atguigu.tingshu.vo.order.OrderInfoVo;
+import com.atguigu.tingshu.vo.order.TradeVo;
+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.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
@@ -16,5 +25,21 @@ public class OrderInfoApiController {
 	private OrderInfoService orderInfoService;
 
 
+	/**
+	 * 对购买商品(VIP会员、专辑、声音)进行结算,渲染结算页
+	 * @param tradeVo
+	 * @return
+	 */
+	@GuiGuLogin
+	@Operation(summary = "对购买商品(VIP会员、专辑、声音)进行结算,渲染结算页")
+	@PostMapping("/orderInfo/trade")
+	public Result<OrderInfoVo> trade(@RequestBody @Validated TradeVo tradeVo) {
+		//1.获取用户ID
+		Long userId = AuthContextHolder.getUserId();
+		//2.调用业务逻辑处理不同商品类别结算页数据
+		OrderInfoVo orderInfoVo = orderInfoService.trade(userId, tradeVo);
+		return Result.ok(orderInfoVo);
+	}
+
 }
 

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

@@ -1,9 +1,18 @@
 package com.atguigu.tingshu.order.service;
 
 import com.atguigu.tingshu.model.order.OrderInfo;
+import com.atguigu.tingshu.vo.order.OrderInfoVo;
+import com.atguigu.tingshu.vo.order.TradeVo;
 import com.baomidou.mybatisplus.extension.service.IService;
 
 public interface OrderInfoService extends IService<OrderInfo> {
 
 
+    /**
+     * 对购买商品(VIP会员、专辑、声音)封装订单结算页所需要数据
+     * @param userId 用户ID
+     * @param tradeVo {购买商品类别,商品ID,声音数量}
+     * @return
+     */
+    OrderInfoVo trade(Long userId, TradeVo tradeVo);
 }

+ 171 - 0
service/service-order/src/main/java/com/atguigu/tingshu/order/service/impl/OrderInfoServiceImpl.java

@@ -1,13 +1,38 @@
 package com.atguigu.tingshu.order.service.impl;
 
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.IdUtil;
+import com.atguigu.tingshu.album.AlbumFeignClient;
+import com.atguigu.tingshu.common.constant.RedisConstant;
+import com.atguigu.tingshu.model.album.AlbumInfo;
 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.OrderInfoMapper;
 import com.atguigu.tingshu.order.service.OrderInfoService;
+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 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.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.concurrent.TimeUnit;
+
+import static com.atguigu.tingshu.common.constant.SystemConstant.*;
+
 @Slf4j
 @Service
 @SuppressWarnings({"all"})
@@ -16,5 +41,151 @@ public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo
     @Autowired
     private OrderInfoMapper orderInfoMapper;
 
+    @Autowired
+    private UserFeignClient userFeignClient;
+
+    @Autowired
+    private AlbumFeignClient albumFeignClient;
+
+    @Autowired
+    private RedisTemplate redisTemplate;
+
+
+    /**
+     * 对购买商品(VIP会员、专辑、声音)封装订单结算页所需要数据
+     *
+     * @param userId  用户ID
+     * @param tradeVo {购买商品类别,商品ID,声音数量}
+     * @return
+     */
+    @Override
+    public OrderInfoVo trade(Long userId, TradeVo tradeVo) {
+        //1.创建订单VO
+        OrderInfoVo orderInfoVo = new OrderInfoVo();
+
+        //1.1.初始化订单结算页三个价格:原价、减免价、订单价
+        BigDecimal originalAmount = new BigDecimal("0.00");
+        BigDecimal derateAmount = new BigDecimal("0.00");
+        BigDecimal orderAmount = new BigDecimal("0.00");
+        //1.2.初始化订单结算页两个集合:商品明细集合、商品优惠集合
+        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 远程调用"用户服务"获取套餐详情
+            VipServiceConfig vipServiceConfig = userFeignClient.getVipServiceConfig(tradeVo.getItemId()).getData();
+            Assert.notNull(vipServiceConfig, "套餐:{}不存在", tradeVo.getItemId());
+            //2.2 计算订单相关价格 订单价=原价-减免价
+            originalAmount = vipServiceConfig.getPrice();
+            orderAmount = vipServiceConfig.getDiscountPrice();
+            derateAmount = originalAmount.subtract(orderAmount);
+
+            //2.3 封装订单明细列表
+            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 封装订单优惠列表
+            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);
+            }
+        } else if (ORDER_ITEM_TYPE_ALBUM.equals(itemType)) {
+            //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 暂时让订单价等于原价
+            originalAmount = price;
+            orderAmount = originalAmount;
+            //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 封装订单明细列表、优惠列表
+            OrderDetailVo orderDetailVo = new OrderDetailVo();
+            orderDetailVo.setItemId(albumId);
+            orderDetailVo.setItemName("专辑:" + albumInfo.getAlbumTitle());
+            orderDetailVo.setItemUrl(albumInfo.getCoverUrl());
+            orderDetailVo.setItemPrice(originalAmount);
+            orderDetailVoList.add(orderDetailVo);
+
+            //3.6 封装订单优惠列表
+            if(originalAmount.compareTo(orderAmount) == 1){
+                OrderDerateVo orderDerateVo = new OrderDerateVo();
+                orderDerateVo.setDerateType(ORDER_DERATE_ALBUM_DISCOUNT);
+                orderDerateVo.setDerateAmount(derateAmount);
+                orderDerateVo.setRemarks("专辑限时减免:"+derateAmount);
+                orderDerateVoList.add(orderDerateVo);
+            }
+        }
+
+        //4.TODO 处理购买商品类别:声音
+
+
+        //5.封装订单VO 包含三类数据:价格相关、商品相关、其他杂项
+        //5.1 为订单价、原价、减免价赋值
+        orderInfoVo.setOriginalAmount(originalAmount);
+        orderInfoVo.setOrderAmount(orderAmount);
+        orderInfoVo.setDerateAmount(derateAmount);
+        //5.2 为商品列表、优惠列表赋值
+        orderInfoVo.setOrderDetailVoList(orderDetailVoList);
+        orderInfoVo.setOrderDerateVoList(orderDerateVoList);
+
+        //5.3 为订单VO其他杂项赋值:购买商品类别、流水号、时间戳及签名
+        orderInfoVo.setItemType(itemType);
+        //5.3.1 为当前用户本次订单生成流水号,将流水号存入Redis,有效时间5分钟,解决订单重复提交问题
+        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);
+
+        //5.3.2 为本次订单生产时间戳及签名
+        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);
+        //6.响应订单VO
+        return orderInfoVo;
+    }
 
 }

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

@@ -3,6 +3,7 @@ package com.atguigu.tingshu.user.api;
 import com.atguigu.tingshu.common.cache.GuiGuCache;
 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.user.service.UserInfoService;
 import com.atguigu.tingshu.vo.user.UserInfoVo;
 import io.swagger.v3.oas.annotations.Operation;
@@ -52,5 +53,36 @@ public class UserInfoApiController {
     }
 
 
+    /**
+     * 验证当前用户是否购买过指定专辑
+     *
+     * @param albumId
+     * @return
+     */
+    @GuiGuLogin
+    @Operation(summary = "验证当前用户是否购买过指定专辑")
+    @GetMapping("/userInfo/isPaidAlbum/{albumId}")
+    public Result<Boolean> isPaidAlbum(@PathVariable("albumId") Long albumId) {
+        //1.获取当前用户ID
+        Long userId = AuthContextHolder.getUserId();
+        //2.调用业务层方法返回指定用户指定专辑购买结果
+        boolean isPaidAlbum = userInfoService.isPaidAlbum(userId, albumId);
+        return Result.ok(isPaidAlbum);
+    }
+
+    /**
+     * 查询用户指定专辑已购声音ID列表
+     * @param albumId
+     * @return
+     */
+    @GuiGuLogin
+    @Operation(summary = "查询用户指定专辑已购声音ID列表")
+    @GetMapping("/userInfo/findUserPaidTrackList/{albumId}")
+    public Result<List<Long>> findUserPaidTrackList(@PathVariable("albumId") Long albumId) {
+        Long userId = AuthContextHolder.getUserId();
+        List<Long> list = userInfoService.findUserPaidTrackList(userId, albumId);
+        return Result.ok(list);
+    }
+
 }
 

+ 23 - 0
service/service-user/src/main/java/com/atguigu/tingshu/user/api/VipServiceConfigApiController.java

@@ -1,11 +1,19 @@
 package com.atguigu.tingshu.user.api;
 
+import com.atguigu.tingshu.common.cache.GuiGuCache;
+import com.atguigu.tingshu.common.result.Result;
+import com.atguigu.tingshu.model.user.VipServiceConfig;
 import com.atguigu.tingshu.user.service.VipServiceConfigService;
+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.PathVariable;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.util.List;
+
 @Tag(name = "VIP服务配置管理接口")
 @RestController
 @RequestMapping("api/user")
@@ -15,5 +23,20 @@ public class VipServiceConfigApiController {
 	@Autowired
 	private VipServiceConfigService vipServiceConfigService;
 
+	@Operation(summary = "查询VIP套餐列表")
+	@GetMapping("/vipServiceConfig/findAll")
+	public Result<List<VipServiceConfig>> findAll(){
+		List<VipServiceConfig> list = vipServiceConfigService.list();
+		return Result.ok(list);
+	}
+
+
+	@GuiGuCache(prefix = "vipServiceConfig:")
+	@Operation(summary = "根据套餐ID查询套餐详情")
+	@GetMapping("/vipServiceConfig/getVipServiceConfig/{id}")
+	public Result<VipServiceConfig> getVipServiceConfig(@PathVariable("id") Long id){
+		VipServiceConfig vipServiceConfig = vipServiceConfigService.getById(id);
+		return Result.ok(vipServiceConfig);
+	}
 }
 

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

@@ -39,4 +39,20 @@ public interface UserInfoService extends IService<UserInfo> {
     Map<Long, Integer> userIsPaidTrack(Long userId, Long albumId, List<Long> needCheckBuyStateTrackIds);
 
 
+    /**
+     * 返回指定用户指定专辑购买结果
+     * @param userId
+     * @param albumId
+     * @return
+     */
+    boolean isPaidAlbum(Long userId, Long albumId);
+
+    /**
+     * 查询用户指定专辑已购声音ID列表
+     *
+     * @param userId
+     * @param albumId
+     * @return
+     */
+    List<Long> findUserPaidTrackList(Long userId, Long albumId);
 }

+ 42 - 3
service/service-user/src/main/java/com/atguigu/tingshu/user/service/impl/UserInfoServiceImpl.java

@@ -174,7 +174,7 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
                                 .eq(UserPaidTrack::getAlbumId, albumId)
                 );
         //2.1 如果已购声音列表不存在,则将提交声音购买情况设置为0,返回即可
-        if(CollUtil.isEmpty(userPaidTrackList)){
+        if (CollUtil.isEmpty(userPaidTrackList)) {
             for (Long needCheckBuyStateTrackId : needCheckBuyStateTrackIds) {
                 map.put(needCheckBuyStateTrackId, 0);
             }
@@ -185,14 +185,53 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
         for (Long needCheckBuyStateTrackId : needCheckBuyStateTrackIds) {
             //2.2.1 已购声音ID集合中包含待检查声音ID,说明该声音已购买 将购买声音ID设置为1
             //2.2.2 反之 将声音ID购买情况设置为0
-            if(userPaidTrackIdList.contains(needCheckBuyStateTrackId)){
+            if (userPaidTrackIdList.contains(needCheckBuyStateTrackId)) {
                 map.put(needCheckBuyStateTrackId, 1);
-            }else{
+            } else {
                 map.put(needCheckBuyStateTrackId, 0);
             }
         }
         return map;
     }
 
+    /**
+     * 返回指定用户指定专辑购买结果
+     *
+     * @param userId
+     * @param albumId
+     * @return
+     */
+    @Override
+    public boolean isPaidAlbum(Long userId, Long albumId) {
+        LambdaQueryWrapper<UserPaidAlbum> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(UserPaidAlbum::getUserId, userId);
+        queryWrapper.eq(UserPaidAlbum::getAlbumId, albumId);
+        Long count = userPaidAlbumMapper.selectCount(queryWrapper);
+        return count > 0;
+    }
+
+    /**
+     * 查询用户指定专辑已购声音ID列表
+     *
+     * @param userId
+     * @param albumId
+     * @return
+     */
+    @Override
+    public List<Long> findUserPaidTrackList(Long userId, Long albumId) {
+        LambdaQueryWrapper<UserPaidTrack> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(UserPaidTrack::getAlbumId, albumId);
+        queryWrapper.eq(UserPaidTrack::getUserId, userId);
+        queryWrapper.select(UserPaidTrack::getTrackId);
+        List<UserPaidTrack> userPaidTrackList = userPaidTrackMapper.selectList(queryWrapper);
+        if(CollUtil.isNotEmpty(userPaidTrackList)){
+            return userPaidTrackList
+                    .stream()
+                    .map(UserPaidTrack::getTrackId)
+                    .collect(Collectors.toList());
+        }
+        return null;
+    }
+
 
 }