소스 검색

day08
feture 专辑详情

it_lv 2 주 전
부모
커밋
f5a7587702
22개의 변경된 파일597개의 추가작업 그리고 52개의 파일을 삭제
  1. 8 0
      common/common-util/src/main/java/com/atguigu/tingshu/common/util/MongoUtil.java
  2. 31 0
      lesson.sql
  3. 5 1
      model/src/main/java/com/atguigu/tingshu/vo/album/TrackStatMqVo.java
  4. 2 2
      model/src/main/java/com/atguigu/tingshu/vo/album/TrackStatVo.java
  5. 10 0
      service-client/service-album-client/src/main/java/com/atguigu/tingshu/album/AlbumFeignClient.java
  6. 6 0
      service-client/service-album-client/src/main/java/com/atguigu/tingshu/album/impl/AlbumDegradeFeignClient.java
  7. 16 0
      service/service-album/src/main/java/com/atguigu/tingshu/album/api/BaseCategoryApiController.java
  8. 14 0
      service/service-album/src/main/java/com/atguigu/tingshu/album/api/TrackInfoApiController.java
  9. 3 0
      service/service-album/src/main/java/com/atguigu/tingshu/album/mapper/TrackInfoMapper.java
  10. 66 0
      service/service-album/src/main/java/com/atguigu/tingshu/album/receiver/AlbumReceiver.java
  11. 14 3
      service/service-album/src/main/java/com/atguigu/tingshu/album/service/TrackInfoService.java
  12. 57 7
      service/service-album/src/main/java/com/atguigu/tingshu/album/service/impl/TrackInfoServiceImpl.java
  13. 9 0
      service/service-album/src/main/resources/mapper/TrackInfoMapper.xml
  14. 27 0
      service/service-search/src/main/java/com/atguigu/tingshu/search/api/SearchApiController.java
  15. 8 0
      service/service-search/src/main/java/com/atguigu/tingshu/search/service/SearchService.java
  16. 67 7
      service/service-search/src/main/java/com/atguigu/tingshu/search/service/impl/SearchServiceImpl.java
  17. 31 28
      service/service-user/src/main/java/com/atguigu/tingshu/user/api/UserInfoApiController.java
  18. 53 2
      service/service-user/src/main/java/com/atguigu/tingshu/user/api/UserListenProcessApiController.java
  19. 2 0
      service/service-user/src/main/java/com/atguigu/tingshu/user/service/UserInfoService.java
  20. 26 0
      service/service-user/src/main/java/com/atguigu/tingshu/user/service/UserListenProcessService.java
  21. 1 0
      service/service-user/src/main/java/com/atguigu/tingshu/user/service/impl/UserInfoServiceImpl.java
  22. 141 2
      service/service-user/src/main/java/com/atguigu/tingshu/user/service/impl/UserListenProcessServiceImpl.java

+ 8 - 0
common/common-util/src/main/java/com/atguigu/tingshu/common/util/MongoUtil.java

@@ -4,13 +4,21 @@ import lombok.Getter;
 
 public class MongoUtil {
 
+    /**
+     * 在MongoDB中业务数据对应集合名称
+     */
     @Getter
     public enum MongoCollectionEnum {
 
+        //专辑订阅
         USER_SUBSCRIBE(100,"userSubscribe"),
+        //声音收藏
         USER_COLLECT(100,"userCollect"),
+        //声音播放进度
         USER_LISTEN_PROCESS(100,"userListenProcess"),
+        //评论
         COMMENT(100,"comment"),
+        //评论点赞
         COMMENT_PRAISE(100,"commentPraise"),
         ;
 

+ 31 - 0
lesson.sql

@@ -178,3 +178,34 @@ explain select
         group by ti.id
         order by ti.id desc;
 
+
+
+# 根据专辑ID查询专辑统计信息
+select * from album_stat where album_id = 1;
+
+explain select
+            album_id,
+            max(if(stat_type='0401', stat_num, 0)) playStatNum,
+            max(if(stat_type='0402', stat_num, 0)) subscribeStatNum,
+            sum(if(stat_type='0403', stat_num, 0)) buyStatNum,
+            max(if(stat_type='0404', stat_num, 0)) commentStatNum
+        from album_stat where album_id = 1 and is_deleted = 0;
+
+
+
+#需求:根据专辑ID,分页获取专辑下声音列表 声音统计信息
+
+explain select
+            ti.id track_id,
+            ti.track_title,
+            ti.media_duration,
+            ti.order_num,
+            ti.create_time,
+            max(if(stat_type='0701', stat_num, 0)) playStatNum,
+            max(if(stat_type='0702', stat_num, 0)) collectStatNum,
+            max(if(stat_type='0703', stat_num, 0)) praiseStatNum,
+            max(if(stat_type='0704', stat_num, 0)) commentStatNum
+        from track_info ti inner join track_stat ts on ts.track_id = ti.id and ts.is_deleted = 0
+        where ti.album_id = 1 and ti.status = '0501' and ti.is_deleted = 0
+        group by ti.id
+        order by ti.id asc;

+ 5 - 1
model/src/main/java/com/atguigu/tingshu/vo/album/TrackStatMqVo.java

@@ -3,9 +3,13 @@ package com.atguigu.tingshu.vo.album;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
+import java.io.Serializable;
+
 @Data
 @Schema(description = "TrackStatMqVo")
-public class TrackStatMqVo {
+public class TrackStatMqVo implements Serializable {
+
+	private static final long serialVersionUID = 1L;
 
 	@Schema(description = "业务编号:去重使用")
 	private String businessNo;

+ 2 - 2
model/src/main/java/com/atguigu/tingshu/vo/album/TrackStatVo.java

@@ -11,10 +11,10 @@ public class TrackStatVo {
 	@Schema(description = "播放量")
 	private Integer playStatNum;
 
-	@Schema(description = "订阅量")
+	@Schema(description = "收藏量")
 	private Integer collectStatNum;
 
-	@Schema(description = "购买量")
+	@Schema(description = "点赞量")
 	private Integer praiseStatNum;
 
 	@Schema(description = "评论数")

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

@@ -3,6 +3,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.vo.album.AlbumStatVo;
@@ -22,6 +23,15 @@ import java.util.List;
 @FeignClient(value = "service-album",path = "api/album",fallback = AlbumDegradeFeignClient.class)
 public interface AlbumFeignClient {
 
+    /**
+     * 查询所有一级分类列表
+     *
+     * @return
+     */
+    @GetMapping("/category/findAllCategory1")
+    public Result<List<BaseCategory1>> getAllCategory1();
+
+
     @GetMapping("/albumInfo/getAlbumInfo/{id}")
     public Result<AlbumInfo> getAlbumInfo(@PathVariable("id") Long id);
 

+ 6 - 0
service-client/service-album-client/src/main/java/com/atguigu/tingshu/album/impl/AlbumDegradeFeignClient.java

@@ -4,6 +4,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.vo.album.AlbumStatVo;
@@ -17,6 +18,11 @@ import java.util.List;
 public class AlbumDegradeFeignClient implements AlbumFeignClient {
 
 
+    @Override
+    public Result<List<BaseCategory1>> getAllCategory1() {
+        return null;
+    }
+
     @Override
     public Result<AlbumInfo> getAlbumInfo(Long id) {
         log.error("[专辑服务]提供远程{}调用执行服务降级", "getAlbumInfo");

+ 16 - 0
service/service-album/src/main/java/com/atguigu/tingshu/album/api/BaseCategoryApiController.java

@@ -4,8 +4,10 @@ import com.alibaba.fastjson.JSONObject;
 import com.atguigu.tingshu.album.service.BaseCategoryService;
 import com.atguigu.tingshu.common.result.Result;
 import com.atguigu.tingshu.model.album.BaseAttribute;
+import com.atguigu.tingshu.model.album.BaseCategory1;
 import com.atguigu.tingshu.model.album.BaseCategory3;
 import com.atguigu.tingshu.model.album.BaseCategoryView;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -89,5 +91,19 @@ public class BaseCategoryApiController {
         JSONObject jsonObject = baseCategoryService.getBaseCategoryListByCategory1Id(category1Id);
         return Result.ok(jsonObject);
     }
+
+    /**
+     * 查询所有一级分类列表
+     *
+     * @return
+     */
+    @Operation(summary = "查询所有一级分类列表")
+    @GetMapping("/category/findAllCategory1")
+    public Result<List<BaseCategory1>> getAllCategory1() {
+        LambdaQueryWrapper<BaseCategory1> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.select(BaseCategory1::getId);
+        List<BaseCategory1> list = baseCategoryService.list(queryWrapper);
+        return Result.ok(list);
+    }
 }
 

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

@@ -10,6 +10,7 @@ import com.atguigu.tingshu.query.album.TrackInfoQuery;
 import com.atguigu.tingshu.vo.album.AlbumTrackListVo;
 import com.atguigu.tingshu.vo.album.TrackInfoVo;
 import com.atguigu.tingshu.vo.album.TrackListVo;
+import com.atguigu.tingshu.vo.album.TrackStatVo;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -147,5 +148,18 @@ public class TrackInfoApiController {
         //4.返回结果
         return Result.ok(pageInfo);
     }
+
+
+    /**
+     * 查询声音统计信息
+     * @param trackId
+     * @return
+     */
+    @Operation(summary = "查询声音统计信息")
+    @GetMapping("/trackInfo/getTrackStatVo/{trackId}")
+    public Result<TrackStatVo> getTrackStatVo(@PathVariable Long trackId){
+        TrackStatVo trackStatVo = trackInfoService.getTrackStatVo(trackId);
+        return Result.ok(trackStatVo);
+    }
 }
 

+ 3 - 0
service/service-album/src/main/java/com/atguigu/tingshu/album/mapper/TrackInfoMapper.java

@@ -4,6 +4,7 @@ import com.atguigu.tingshu.model.album.TrackInfo;
 import com.atguigu.tingshu.query.album.TrackInfoQuery;
 import com.atguigu.tingshu.vo.album.AlbumTrackListVo;
 import com.atguigu.tingshu.vo.album.TrackListVo;
+import com.atguigu.tingshu.vo.album.TrackStatVo;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import org.apache.ibatis.annotations.Mapper;
@@ -29,4 +30,6 @@ public interface TrackInfoMapper extends BaseMapper<TrackInfo> {
      * @return
      */
     Page<AlbumTrackListVo> findAlbumTrackPage(Page<AlbumTrackListVo> pageInfo,@Param("albumId") Long albumId);
+
+    TrackStatVo getTrackStatVo(@Param("trackId") Long trackId);
 }

+ 66 - 0
service/service-album/src/main/java/com/atguigu/tingshu/album/receiver/AlbumReceiver.java

@@ -0,0 +1,66 @@
+package com.atguigu.tingshu.album.receiver;
+
+import com.atguigu.tingshu.album.service.TrackInfoService;
+import com.atguigu.tingshu.common.rabbit.constant.MqConst;
+import com.atguigu.tingshu.vo.album.TrackStatMqVo;
+import com.rabbitmq.client.Channel;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.amqp.core.Message;
+import org.springframework.amqp.rabbit.annotation.Exchange;
+import org.springframework.amqp.rabbit.annotation.Queue;
+import org.springframework.amqp.rabbit.annotation.QueueBinding;
+import org.springframework.amqp.rabbit.annotation.RabbitListener;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author: atguigu
+ * @create: 2025-03-18 14:19
+ */
+@Slf4j
+@Component
+public class AlbumReceiver {
+
+    @Autowired
+    private RedisTemplate redisTemplate;
+
+    @Autowired
+    private TrackInfoService trackInfoService;
+
+    /**
+     * 更新声音统计,包含专辑统计数值
+     *
+     * @param mqVo
+     * @param message
+     * @param channel
+     */
+    @SneakyThrows
+    @RabbitListener(bindings = @QueueBinding(
+            exchange = @Exchange(value = MqConst.EXCHANGE_TRACK, durable = "true"),
+            value = @Queue(value = MqConst.QUEUE_TRACK_STAT_UPDATE, durable = "true"),
+            key = {MqConst.ROUTING_TRACK_STAT_UPDATE}
+    ))
+    public void updateTrackStat(TrackStatMqVo mqVo, Message message, Channel channel) {
+        if (mqVo != null) {
+            log.info("[专辑服务]更新声音统计,收到消息:{}", mqVo);
+            //1.幂等性处理 避免同一个消息被多次处理造成多次更新统计数值
+            String redisKey = "track:stat:db:" + mqVo.getBusinessNo();
+            Boolean flag = redisTemplate.opsForValue().setIfAbsent(redisKey, "1", 5, TimeUnit.MINUTES);
+            if (flag) {
+                try {
+                    //2.更新声音统计
+                    trackInfoService.updateTrackStat(mqVo);
+                } catch (Exception e) {
+                    log.error("更新声音统计异常:{}", e.getMessage());
+                    redisTemplate.delete(redisKey);
+                    channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
+                }
+            }
+        }
+        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
+    }
+}

+ 14 - 3
service/service-album/src/main/java/com/atguigu/tingshu/album/service/TrackInfoService.java

@@ -2,9 +2,7 @@ package com.atguigu.tingshu.album.service;
 
 import com.atguigu.tingshu.model.album.TrackInfo;
 import com.atguigu.tingshu.query.album.TrackInfoQuery;
-import com.atguigu.tingshu.vo.album.AlbumTrackListVo;
-import com.atguigu.tingshu.vo.album.TrackInfoVo;
-import com.atguigu.tingshu.vo.album.TrackListVo;
+import com.atguigu.tingshu.vo.album.*;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.IService;
 
@@ -51,4 +49,17 @@ public interface TrackInfoService extends IService<TrackInfo> {
      * @return
      */
     Page<AlbumTrackListVo> findAlbumTrackPage(Page<AlbumTrackListVo> pageInfo, Long albumId, Long userId);
+
+    /**
+     * 查询声音统计信息
+     * @param trackId
+     * @return
+     */
+    TrackStatVo getTrackStatVo(Long trackId);
+
+    /**
+     * 更新数据库中声音表以及专辑表统计数值
+     * @param mqVo
+     */
+    void updateTrackStat(TrackStatMqVo mqVo);
 }

+ 57 - 7
service/service-album/src/main/java/com/atguigu/tingshu/album/service/impl/TrackInfoServiceImpl.java

@@ -3,20 +3,19 @@ package com.atguigu.tingshu.album.service.impl;
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollUtil;
 import com.atguigu.tingshu.album.mapper.AlbumInfoMapper;
+import com.atguigu.tingshu.album.mapper.AlbumStatMapper;
 import com.atguigu.tingshu.album.mapper.TrackInfoMapper;
 import com.atguigu.tingshu.album.mapper.TrackStatMapper;
 import com.atguigu.tingshu.album.service.TrackInfoService;
 import com.atguigu.tingshu.album.service.VodService;
 import com.atguigu.tingshu.common.constant.SystemConstant;
 import com.atguigu.tingshu.model.album.AlbumInfo;
+import com.atguigu.tingshu.model.album.AlbumStat;
 import com.atguigu.tingshu.model.album.TrackInfo;
 import com.atguigu.tingshu.model.album.TrackStat;
 import com.atguigu.tingshu.query.album.TrackInfoQuery;
 import com.atguigu.tingshu.user.client.UserFeignClient;
-import com.atguigu.tingshu.vo.album.AlbumTrackListVo;
-import com.atguigu.tingshu.vo.album.TrackInfoVo;
-import com.atguigu.tingshu.vo.album.TrackListVo;
-import com.atguigu.tingshu.vo.album.TrackMediaInfoVo;
+import com.atguigu.tingshu.vo.album.*;
 import com.atguigu.tingshu.vo.user.UserInfoVo;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
@@ -57,6 +56,9 @@ public class TrackInfoServiceImpl extends ServiceImpl<TrackInfoMapper, TrackInfo
     @Autowired
     private UserFeignClient userFeignClient;
 
+    @Autowired
+    private AlbumStatMapper albumStatMapper;
+
     /**
      * 保存声音,发起内容审核任务
      *
@@ -253,7 +255,7 @@ public class TrackInfoServiceImpl extends ServiceImpl<TrackInfoMapper, TrackInfo
                 needCheckBuyState = true;
             }
             //3.2.2 所有用户查看付费类型为“付费”专辑,进一步查询当前页面中声音购买情况
-            if(ALBUM_PAY_TYPE_REQUIRE.equals(payType)){
+            if (ALBUM_PAY_TYPE_REQUIRE.equals(payType)) {
                 needCheckBuyState = true;
             }
 
@@ -268,16 +270,64 @@ public class TrackInfoServiceImpl extends ServiceImpl<TrackInfoMapper, TrackInfo
                 //3.3.2 远程调用用户服务获取每个声音购买情况
                 Map<Long, Integer> trackBuyStateMap = userFeignClient.userIsPaidTrack(userId, albumId, needCheckBuyStateTrackIds).getData();
                 //3.4 根据购买情况设置付费标识(去除试听声音ID),未购买声音将标识设置为:true
-                if(CollUtil.isNotEmpty(trackBuyStateMap)){
+                if (CollUtil.isNotEmpty(trackBuyStateMap)) {
                     pageInfo.getRecords()
                             .stream()
                             .filter(track -> track.getOrderNum() > tracksForFree)
-                            .forEach(albumTrackVo-> albumTrackVo.setIsShowPaidMark(trackBuyStateMap.get(albumTrackVo.getTrackId())==0));
+                            .forEach(albumTrackVo -> albumTrackVo.setIsShowPaidMark(trackBuyStateMap.get(albumTrackVo.getTrackId()) == 0));
                 }
             }
         }
         return pageInfo;
     }
 
+    /**
+     * 查询声音统计信息
+     *
+     * @param trackId
+     * @return
+     */
+    @Override
+    public TrackStatVo getTrackStatVo(Long trackId) {
+        return trackInfoMapper.getTrackStatVo(trackId);
+    }
+
+    /**
+     * 更新数据库中声音表以及专辑表统计数值
+     *
+     * @param mqVo
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateTrackStat(TrackStatMqVo mqVo) {
+        //1.更新声音表统计数值
+        trackStatMapper.update(
+                null,
+                new LambdaUpdateWrapper<TrackStat>()
+                        .eq(TrackStat::getTrackId, mqVo.getTrackId())
+                        .eq(TrackStat::getStatType, mqVo.getStatType())
+                        .setSql("stat_num = stat_num + " + mqVo.getCount())
+        );
+        //2.如果统计类型是:播放量、评论量 同时更新所属专辑统计数值
+        if(SystemConstant.TRACK_STAT_PLAY.equals(mqVo.getStatType())){
+            albumStatMapper.update(
+                    null,
+                    new LambdaUpdateWrapper<AlbumStat>()
+                            .eq(AlbumStat::getAlbumId, mqVo.getAlbumId())
+                            .eq(AlbumStat::getStatType, SystemConstant.ALBUM_STAT_PLAY)
+                            .setSql("stat_num = stat_num + " + mqVo.getCount())
+            );
+        }
+        if(SystemConstant.TRACK_STAT_COMMENT.equals(mqVo.getStatType())){
+            albumStatMapper.update(
+                    null,
+                    new LambdaUpdateWrapper<AlbumStat>()
+                            .eq(AlbumStat::getAlbumId, mqVo.getAlbumId())
+                            .eq(AlbumStat::getStatType, SystemConstant.ALBUM_STAT_COMMENT)
+                            .setSql("stat_num = stat_num + " + mqVo.getCount())
+            );
+        }
+    }
+
 
 }

+ 9 - 0
service/service-album/src/main/resources/mapper/TrackInfoMapper.xml

@@ -50,5 +50,14 @@
         group by ti.id
         order by ti.id asc
     </select>
+    <select id="getTrackStatVo" resultType="com.atguigu.tingshu.vo.album.TrackStatVo">
+        select
+            track_id,
+            max(if(stat_type='0701', stat_num, 0)) playStatNum,
+            max(if(stat_type='0702', stat_num, 0)) collectStatNum,
+            sum(if(stat_type='0703', stat_num, 0)) praiseStatNum,
+            max(if(stat_type='0704', stat_num, 0)) commentStatNum
+        from track_stat where track_id = #{trackId} and is_deleted = 0
+    </select>
 </mapper>
 

+ 27 - 0
service/service-search/src/main/java/com/atguigu/tingshu/search/api/SearchApiController.java

@@ -3,6 +3,7 @@ package com.atguigu.tingshu.search.api;
 import com.atguigu.tingshu.common.result.Result;
 import com.atguigu.tingshu.query.search.AlbumIndexQuery;
 import com.atguigu.tingshu.search.service.SearchService;
+import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
 import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -80,5 +81,31 @@ public class SearchApiController {
         List<String> list = searchService.completeSuggest(keyword);
         return Result.ok(list);
     }
+
+
+    /**
+     * 手动更新Redis中小时榜数据
+     * @return
+     */
+    @Operation(summary = "手动更新Redis中小时榜数据")
+    @GetMapping("/albumInfo/updateLatelyAlbumRanking")
+    public Result updateLatelyAlbumRanking(){
+        searchService.updateLatelyAlbumRanking();
+        return Result.ok();
+    }
+
+
+    /**
+     * 获取不同分类下不同排行维度TOP20
+     * @param category1Id
+     * @param dimension
+     * @return
+     */
+    @Operation(summary = "获取不同分类下不同排行维度TOP20")
+    @GetMapping("/albumInfo/findRankingList/{category1Id}/{dimension}")
+    public Result<List<AlbumInfoIndexVo>> getRankingList(@PathVariable Long category1Id, @PathVariable String dimension) {
+        List<AlbumInfoIndexVo> list = searchService.getRankingList(category1Id, dimension);
+        return Result.ok(list);
+    }
 }
 

+ 8 - 0
service/service-search/src/main/java/com/atguigu/tingshu/search/service/SearchService.java

@@ -5,6 +5,7 @@ import co.elastic.clients.elasticsearch.core.SearchResponse;
 import com.atguigu.tingshu.model.search.AlbumInfoIndex;
 import com.atguigu.tingshu.model.search.SuggestIndex;
 import com.atguigu.tingshu.query.search.AlbumIndexQuery;
+import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
 import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
 
 import java.util.Collection;
@@ -76,4 +77,11 @@ public interface SearchService {
      * @return
      */
     Collection<String> parseSuggestResult(SearchResponse<SuggestIndex> suggestIndexSearchResponse, String suggestName);
+
+    /**
+     * 手动更新Redis中小时榜数据
+     */
+    void updateLatelyAlbumRanking();
+
+    List<AlbumInfoIndexVo> getRankingList(Long category1Id, String dimension);
 }

+ 67 - 7
service/service-search/src/main/java/com/atguigu/tingshu/search/service/impl/SearchServiceImpl.java

@@ -19,10 +19,8 @@ import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
 import co.elastic.clients.elasticsearch.core.search.Suggestion;
 import com.alibaba.fastjson.JSON;
 import com.atguigu.tingshu.album.AlbumFeignClient;
-import com.atguigu.tingshu.model.album.AlbumAttributeValue;
-import com.atguigu.tingshu.model.album.AlbumInfo;
-import com.atguigu.tingshu.model.album.BaseCategory3;
-import com.atguigu.tingshu.model.album.BaseCategoryView;
+import com.atguigu.tingshu.common.constant.RedisConstant;
+import com.atguigu.tingshu.model.album.*;
 import com.atguigu.tingshu.model.search.AlbumInfoIndex;
 import com.atguigu.tingshu.model.search.AttributeValueIndex;
 import com.atguigu.tingshu.model.search.SuggestIndex;
@@ -38,6 +36,8 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.elasticsearch.core.suggest.Completion;
+import org.springframework.data.redis.core.BoundHashOperations;
+import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
 
 import java.io.IOException;
@@ -445,9 +445,9 @@ public class SearchServiceImpl implements SearchService {
             SearchResponse<SuggestIndex> suggestIndexSearchResponse = elasticsearchClient.search(
                     s -> s.index(SUGGEST_INDEX_NAME)
                             .suggest(s1 -> s1.suggesters(
-                                            "letter-suggest", s2 -> s2.prefix(keyword).completion(c -> c.field("keywordSequence").size(10).skipDuplicates(true))
-                                    ).suggesters("pinyin-suggest", s2 -> s2.prefix(keyword).completion(c -> c.field("keywordPinyin").size(10).skipDuplicates(true).fuzzy(f -> f.fuzziness("2").minLength(4))))
-                                    .suggesters("keyword-suggest", s2 -> s2.prefix(keyword).completion(c -> c.field("keyword").size(10).skipDuplicates(true).fuzzy(f -> f.fuzziness("2").minLength(4))))
+                                                    "letter-suggest", s2 -> s2.prefix(keyword).completion(c -> c.field("keywordSequence").size(10).skipDuplicates(true))
+                                            ).suggesters("pinyin-suggest", s2 -> s2.prefix(keyword).completion(c -> c.field("keywordPinyin").size(10).skipDuplicates(true).fuzzy(f -> f.fuzziness("2").minLength(4))))
+                                            .suggesters("keyword-suggest", s2 -> s2.prefix(keyword).completion(c -> c.field("keyword").size(10).skipDuplicates(true).fuzzy(f -> f.fuzziness("2").minLength(4))))
                             )
                     ,
                     SuggestIndex.class
@@ -507,4 +507,64 @@ public class SearchServiceImpl implements SearchService {
         }
         return list;
     }
+
+    @Autowired
+    private RedisTemplate redisTemplate;
+
+    /**
+     * 手动更新Redis中小时榜数据
+     */
+    @Override
+    public void updateLatelyAlbumRanking() {
+        try {
+            //1.远程调用专辑服务获取所有1级分类ID列表
+            List<BaseCategory1> baseCategory1List = albumFeignClient.getAllCategory1().getData();
+            if (CollUtil.isNotEmpty(baseCategory1List)) {
+                List<Long> category1IdList = baseCategory1List.stream().map(BaseCategory1::getId).collect(Collectors.toList());
+                //2.处理不同1级分类Redis中小时榜数据
+                for (Long category1Id : category1IdList) {
+                    //2.1 构建Redis中每个1级分类Hash小时榜数据Key 创建绑定操作hash对象
+                    String redisKey = RedisConstant.RANKING_KEY_PREFIX + category1Id;
+                    BoundHashOperations<String, String, List<AlbumInfoIndex>> hashOps = redisTemplate.boundHashOps(redisKey);
+                    //2.2 遍历1级分类列表,内部按照5种不同排序方式遍历
+                    String[] rankingDimensionArray = new String[]{"hotScore", "playStatNum", "subscribeStatNum", "buyStatNum", "commentStatNum"};
+                    for (String rankingDimension : rankingDimensionArray) {
+                        //2.2.1 根据1级分类ID+排序方式+获取TOP20检索ES
+                        SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(
+                                s -> s.index(INDEX_NAME).size(20)
+                                        .query(q -> q.term(t -> t.field("category1Id").value(category1Id)))
+                                        .sort(s1 -> s1.field(f -> f.field(rankingDimension).order(SortOrder.Desc)))
+                                        .source(s1 -> s1.filter(f -> f.excludes("attributeValueIndexList", "hotScore", "commentStatNum", "buyStatNum", "subscribeStatNum", "announcerName")))
+                                ,
+                                AlbumInfoIndex.class
+                        );
+                        //2.2.2 解析ES响应结果将解析结果存入Redis
+                        List<Hit<AlbumInfoIndex>> hits = searchResponse.hits().hits();
+                        if(CollUtil.isNotEmpty(hits)){
+                            List<AlbumInfoIndex> top20List =
+                                    hits.stream().map(hit -> hit.source()).collect(Collectors.toList());
+                            hashOps.put(rankingDimension, top20List);
+                        }
+                    }
+
+                }
+            }
+        } catch (IOException e) {
+            log.error("[搜索服务]手动更新Redis中小时榜数据异常:{}", e);
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public List<AlbumInfoIndexVo> getRankingList(Long category1Id, String dimension) {
+        String redisKey = RedisConstant.RANKING_KEY_PREFIX + category1Id;
+        BoundHashOperations<String, String, List<AlbumInfoIndex>> hashOps = redisTemplate.boundHashOps(redisKey);
+        List<AlbumInfoIndex> list = hashOps.get(dimension);
+        if(CollUtil.isNotEmpty(list)){
+            List<AlbumInfoIndexVo> list1 = list.stream().map(albumInfoIndex -> BeanUtil.copyProperties(albumInfoIndex, AlbumInfoIndexVo.class))
+                    .collect(Collectors.toList());
+            return list1;
+        }
+        return null;
+    }
 }

+ 31 - 28
service/service-user/src/main/java/com/atguigu/tingshu/user/api/UserInfoApiController.java

@@ -17,33 +17,36 @@ import java.util.Map;
 @SuppressWarnings({"all"})
 public class UserInfoApiController {
 
-	@Autowired
-	private UserInfoService userInfoService;
-
-	@Operation(summary = "查询用户基本信息")
-	@GetMapping("/userInfo/getUserInfoVo/{userId}")
-	public Result<UserInfoVo> getUserInfoVo(@PathVariable("userId") Long userId) {
-		UserInfoVo userInfoVo = userInfoService.getUserInfo(userId);
-		return Result.ok(userInfoVo);
-	}
-
-
-	/**
-	 * 根据用户ID+专辑ID+声音ID列表查询得到声音购买情况
-	 * @param userId
-	 * @param albumId
-	 * @param needCheckBuyStateTrackIds
-	 * @return
-	 */
-	@Operation(summary = "根据专辑ID+声音ID列表查询得到声音购买情况")
-	@PostMapping("/userInfo/userIsPaidTrack/{userId}/{albumId}")
-	public Result<Map<Long, Integer>> userIsPaidTrack(
-			@PathVariable("userId") Long userId,
-			@PathVariable("albumId") Long albumId,
-			@RequestBody List<Long> needCheckBuyStateTrackIds
-			){
-		Map<Long, Integer> map = userInfoService.userIsPaidTrack(userId, albumId, needCheckBuyStateTrackIds);
-		return Result.ok(map);
-	}
+    @Autowired
+    private UserInfoService userInfoService;
+
+    @Operation(summary = "查询用户基本信息")
+    @GetMapping("/userInfo/getUserInfoVo/{userId}")
+    public Result<UserInfoVo> getUserInfoVo(@PathVariable("userId") Long userId) {
+        UserInfoVo userInfoVo = userInfoService.getUserInfo(userId);
+        return Result.ok(userInfoVo);
+    }
+
+
+    /**
+     * 根据用户ID+专辑ID+声音ID列表查询得到声音购买情况
+     *
+     * @param userId
+     * @param albumId
+     * @param needCheckBuyStateTrackIds
+     * @return
+     */
+    @Operation(summary = "根据专辑ID+声音ID列表查询得到声音购买情况")
+    @PostMapping("/userInfo/userIsPaidTrack/{userId}/{albumId}")
+    public Result<Map<Long, Integer>> userIsPaidTrack(
+            @PathVariable("userId") Long userId,
+            @PathVariable("albumId") Long albumId,
+            @RequestBody List<Long> needCheckBuyStateTrackIds
+    ) {
+        Map<Long, Integer> map = userInfoService.userIsPaidTrack(userId, albumId, needCheckBuyStateTrackIds);
+        return Result.ok(map);
+    }
+
+
 }
 

+ 53 - 2
service/service-user/src/main/java/com/atguigu/tingshu/user/api/UserListenProcessApiController.java

@@ -1,10 +1,17 @@
 package com.atguigu.tingshu.user.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.user.service.UserListenProcessService;
+import com.atguigu.tingshu.vo.user.UserListenProcessVo;
+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.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
+
+import java.math.BigDecimal;
+import java.util.Map;
 
 @Tag(name = "用户声音播放进度管理接口")
 @RestController
@@ -15,5 +22,49 @@ public class UserListenProcessApiController {
 	@Autowired
 	private UserListenProcessService userListenProcessService;
 
+
+	/**
+	 * 业务需求:用户未登录,收听某个声音,引导用户登录
+	 * @param trackId
+	 * @return
+	 */
+	@GuiGuLogin//(required = false)
+	@Operation(summary = "获取声音播放进度")
+	@GetMapping("/userListenProcess/getTrackBreakSecond/{trackId}")
+	public Result<BigDecimal> getTrackBreakSecond(@PathVariable("trackId") Long trackId) {
+		//获取当前用户ID
+		Long userId = AuthContextHolder.getUserId();
+		BigDecimal breakSecond = userListenProcessService.getTrackBreakSecond(userId, trackId);
+		return Result.ok(breakSecond);
+	}
+
+
+	/**
+	 * 业务需求:更新播放进度
+	 * @param userListenProcessVo
+	 * @return
+	 */
+	@GuiGuLogin
+	@Operation(summary = "更新播放进度")
+	@PostMapping("/userListenProcess/updateListenProcess")
+	public Result updateListenProcess(@RequestBody UserListenProcessVo userListenProcessVo) {
+		userListenProcessService.updateListenProcess(userListenProcessVo);
+		return Result.ok();
+	}
+
+
+
+	/**
+	 * 获取最近播放专辑及声音
+	 * @return
+	 */
+	@GuiGuLogin
+	@Operation(summary = "获取最近播放专辑及声音")
+	@GetMapping("/userListenProcess/getLatelyTrack")
+	public Result<Map<String, Long>> getLatelyTrack(){
+		Long userId = AuthContextHolder.getUserId();
+		Map<String, Long> map = userListenProcessService.getLatelyTrack(userId);
+		return Result.ok(map);
+	}
 }
 

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

@@ -37,4 +37,6 @@ public interface UserInfoService extends IService<UserInfo> {
      * @return
      */
     Map<Long, Integer> userIsPaidTrack(Long userId, Long albumId, List<Long> needCheckBuyStateTrackIds);
+
+
 }

+ 26 - 0
service/service-user/src/main/java/com/atguigu/tingshu/user/service/UserListenProcessService.java

@@ -1,5 +1,31 @@
 package com.atguigu.tingshu.user.service;
 
+import com.atguigu.tingshu.vo.user.UserListenProcessVo;
+
+import java.math.BigDecimal;
+import java.util.Map;
+
 public interface UserListenProcessService {
 
+    /**
+     * 获取指定用户声音播放进度
+     * @param userId
+     * @param trackId
+     * @return
+     */
+    BigDecimal getTrackBreakSecond(Long userId, Long trackId);
+
+    /**
+     * 业务需求:更新播放进度
+     * @param userListenProcessVo
+     * @return
+     */
+    void updateListenProcess(UserListenProcessVo userListenProcessVo);
+
+    /**
+     * 获取最近播放专辑及声音
+     * @param userId
+     * @return
+     */
+    Map<String, Long> getLatelyTrack(Long userId);
 }

+ 1 - 0
service/service-user/src/main/java/com/atguigu/tingshu/user/service/impl/UserInfoServiceImpl.java

@@ -191,4 +191,5 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
         return map;
     }
 
+
 }

+ 141 - 2
service/service-user/src/main/java/com/atguigu/tingshu/user/service/impl/UserListenProcessServiceImpl.java

@@ -1,15 +1,154 @@
 package com.atguigu.tingshu.user.service.impl;
 
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.IdUtil;
+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.common.util.MongoUtil;
+import com.atguigu.tingshu.model.user.UserListenProcess;
 import com.atguigu.tingshu.user.service.UserListenProcessService;
+import com.atguigu.tingshu.vo.album.TrackStatMqVo;
+import com.atguigu.tingshu.vo.user.UserListenProcessVo;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Sort;
 import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
 
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import static com.atguigu.tingshu.common.constant.RedisConstant.USER_TRACK_REPEAT_STAT_PREFIX;
+import static com.atguigu.tingshu.common.util.MongoUtil.MongoCollectionEnum.USER_LISTEN_PROCESS;
+
 @Service
 @SuppressWarnings({"all"})
 public class UserListenProcessServiceImpl implements UserListenProcessService {
 
-	@Autowired
-	private MongoTemplate mongoTemplate;
+    @Autowired
+    private MongoTemplate mongoTemplate;
+
+    @Autowired
+    private RedisTemplate redisTemplate;
+
+    @Autowired
+    private RabbitService rabbitService;
+
+    /**
+     * 从MongoDB获取声音播放进度
+     *
+     * @param userId  用户ID
+     * @param trackId 声音ID
+     * @return
+     */
+    @Override
+    public BigDecimal getTrackBreakSecond(Long userId, Long trackId) {
+        //1.获取当前用户的播放进度集合名称 userListenProcess:用户ID
+        String collectionName = MongoUtil.getCollectionName(USER_LISTEN_PROCESS, userId);
+
+        //2.根据用户ID+声音ID查询播放进度
+        Query query = new Query(Criteria.where("userId").is(userId).and("trackId").is(trackId));
+        UserListenProcess userListenProcess = mongoTemplate.findOne(query, UserListenProcess.class, collectionName);
+
+        //3.如果存在记录则返回上次播放时间
+        if (userListenProcess != null) {
+            return userListenProcess.getBreakSecond();
+        }
+        //4.如果不存在记录则返回0
+        return BigDecimal.valueOf(0);
+    }
+
+    /**
+     * 业务需求:更新播放进度
+     * 当前用户收听某个声音播放进度前端使用定时器每隔10S进行调用
+     *
+     * @param userListenProcessVo
+     * @return
+     */
+    @Override
+    public void updateListenProcess(UserListenProcessVo userListenProcessVo) {
+        Long userId = AuthContextHolder.getUserId();
+        //1.获取当前用户的播放进度集合名称 userListenProcess:用户ID
+        String collectionName = MongoUtil.getCollectionName(USER_LISTEN_PROCESS, userId);
+
+        //2.根据声音ID跟用户ID查询播放进度
+        Query query = new Query(Criteria.where("userId").is(userId).and("trackId").is(userListenProcessVo.getTrackId()));
+        UserListenProcess userListenProcess = mongoTemplate.findOne(query, UserListenProcess.class, collectionName);
+
+        //3.如果播放记录为空 则保存一条播放进度
+        userListenProcessVo.setBreakSecond(userListenProcessVo.getBreakSecond().setScale(0, RoundingMode.HALF_UP));
+        if (userListenProcess == null) {
+            //3.1 将VO中参数拷贝到PO对象
+            userListenProcess = BeanUtil.copyProperties(userListenProcessVo, UserListenProcess.class);
+            //3.2 设置用户ID
+            userListenProcess.setCreateTime(new Date());
+            userListenProcess.setUpdateTime(new Date());
+            userListenProcess.setUserId(userId);
+        } else {
+            //4.如果播放记录存在 则更新播放进度
+            userListenProcess.setBreakSecond(userListenProcessVo.getBreakSecond());
+            userListenProcess.setUpdateTime(new Date());
+        }
+        mongoTemplate.save(userListenProcess, collectionName);
+
+        //5.TODO 更新声音/专辑统计数值:播放 更新目标数据源:1.MySQL(专辑服务) 2.ElasticSearch(搜索服务)
+        //5.1 确保某个用户,当日内只能更新一次播放量
+        //5.1.1 构建key形式:前缀:用户ID:声音ID
+        String redisKey = USER_TRACK_REPEAT_STAT_PREFIX + userId + ":" + userListenProcessVo.getTrackId();
+        //5.1.2 尝试采用set nx命令存入Redis 有效时间:当日结束时间-当前时间
+        long ttl = DateUtil.endOfDay(new Date()).getTime() - System.currentTimeMillis();
+        Boolean flag = redisTemplate.opsForValue().setIfAbsent(redisKey, "1", ttl, TimeUnit.MILLISECONDS);
+        //5.2 如果是当日内首次 基于MQ消息 则更新播放量
+        if (flag) {
+            //5.2.1 构建更新统计数值VO对象
+            TrackStatMqVo mqVo = new TrackStatMqVo();
+            mqVo.setAlbumId(userListenProcessVo.getAlbumId());
+            mqVo.setTrackId(userListenProcessVo.getTrackId());
+            mqVo.setStatType(SystemConstant.TRACK_STAT_PLAY);
+            mqVo.setCount(1);
+            //生成消息唯一标识:目的在消费者端确保幂等性
+            String msgId = IdUtil.randomUUID();
+            mqVo.setBusinessNo(msgId);
+            //5.2.2 发送MQ消息通知专辑服务、搜索服务各自更新各自库中统计数值
+            rabbitService.sendMessage(MqConst.EXCHANGE_TRACK, MqConst.ROUTING_TRACK_STAT_UPDATE, mqVo);
+        }
+    }
+
+    /**
+     * 获取最近播放专辑及声音
+     *
+     * @param userId
+     * @return
+     */
+    @Override
+    public Map<String, Long> getLatelyTrack(Long userId) {
+        //1.获取当前用户的播放进度集合名称 userListenProcess:用户ID
+        String collectionName = MongoUtil.getCollectionName(USER_LISTEN_PROCESS, userId);
+
+        //2.根据声音ID跟用户ID查询播放进度
+        Query query = new Query(Criteria.where("userId").is(userId));
+        query.with(Sort.by(Sort.Direction.DESC, "updateTime"));
+        query.limit(1);
+        UserListenProcess listenProcess = mongoTemplate.findOne(query, UserListenProcess.class, collectionName);
+        if (listenProcess != null) {
+            Map<String, Long> map = new HashMap<>();
+            map.put("albumId", listenProcess.getAlbumId());
+            map.put("trackId", listenProcess.getTrackId());
+            return map;
+        }
+        return null;
+    }
 
+    public static void main(String[] args) {
+        System.out.println();
+    }
 }