it_lv 3 hete
szülő
commit
c521fdacbf
1 módosított fájl, 171 hozzáadás és 159 törlés
  1. 171 159
      第5章 详情介绍.md

+ 171 - 159
第5章 详情介绍.md

@@ -717,22 +717,23 @@ limit 0,10;
 
 ```java
 /**
- * 查询当前用户指定声音播放进度
+ * 获取声音播放进度
  *
- * @param trackId 声音ID
- * @return 前端必须返回具体数值,返回null导致前端无法触发更新播放进度定时任务
+ * @param trackId
+ * @return
  */
 @GuiGuLogin(required = false)
+@Operation(summary = "获取声音播放进度")
 @GetMapping("/userListenProcess/getTrackBreakSecond/{trackId}")
 public Result<BigDecimal> getTrackBreakSecond(@PathVariable Long trackId) {
     //1.获取当前用户ID
     Long userId = AuthContextHolder.getUserId();
+    //2.如果用户登录才查询上次播放进度
     if (userId != null) {
-        //2.根据用户ID+声音ID查询播放进度
-        BigDecimal bigDecimal = userListenProcessService.getTrackBreakSecond(userId, trackId);
-        return Result.ok(bigDecimal);
+        BigDecimal breakSecond = userInfoService.getTrackBreakSecond(userId, trackId);
+        return Result.ok(breakSecond);
     }
-    return Result.ok(new BigDecimal("0.00"));
+    return Result.ok(BigDecimal.valueOf(0));
 }
 ```
 
@@ -740,7 +741,7 @@ public Result<BigDecimal> getTrackBreakSecond(@PathVariable Long trackId) {
 
 ```java
 /**
- * 根据用户ID及声音ID查询播放进度
+ * 根据用户ID+声音ID查询上次播放秒数
  * @param userId
  * @param trackId
  * @return
@@ -784,8 +785,8 @@ public class UserListenProcessServiceImpl implements UserListenProcessService {
     @Autowired
     private MongoTemplate mongoTemplate;
 
-    /**
-     * 根据用户ID及声音ID查询播放进度
+       /**
+     * 从MongoDB查询,根据用户ID+声音ID查询上次播放秒数
      *
      * @param userId
      * @param trackId
@@ -793,16 +794,17 @@ public class UserListenProcessServiceImpl implements UserListenProcessService {
      */
     @Override
     public BigDecimal getTrackBreakSecond(Long userId, Long trackId) {
-        //1.创建查询对象:封装查询条件
+        //1.根据用户ID确定操作集合名称 形式为:固定前缀_用户ID=userListenProcess_userId
+        String collectionName = this.getCollectionName(userId);
+        //2.查询条件:用户ID+声音ID
         Query query = new Query();
         query.addCriteria(Criteria.where("userId").is(userId).and("trackId").is(trackId));
-        //2.执行查询:注意每个用户都有自己播放进度集合
-        String collectionName = MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_LISTEN_PROCESS, userId);
+        //3.执行查询MongoDB
         UserListenProcess listenProcess = mongoTemplate.findOne(query, UserListenProcess.class, collectionName);
         if (listenProcess != null) {
             return listenProcess.getBreakSecond();
         }
-        return new BigDecimal("0.00");
+        return BigDecimal.valueOf(0);
     }
 }
 ```
@@ -825,34 +827,33 @@ public class UserListenProcessServiceImpl implements UserListenProcessService {
 
 ```java
 /**
- * 保存更新声音播放进度
+ * 更新声音播放进度
  * @param userListenProcessVo
  * @return
  */
-@Operation(summary = "保存更新声音播放进度")
 @GuiGuLogin(required = false)
+@Operation(summary = "更新声音播放进度")
 @PostMapping("/userListenProcess/updateListenProcess")
 public Result updateListenProcess(@RequestBody UserListenProcessVo userListenProcessVo) {
     //1.获取当前用户ID
     Long userId = AuthContextHolder.getUserId();
+    //2.如果用户登录才查询上次播放进度
     if (userId != null) {
-        //2.新增或修改播放进度
-        userListenProcessService.updateListenProcess(userId, userListenProcessVo);
+        userInfoService.updateUserListenProcess(userId, userListenProcessVo);
     }
     return Result.ok();
 }
-
 ```
 
 **UserListenProcessService**接口:
 
 ```java
 /**
- * 保存更新声音播放进度
+ * 更新声音播放进度
  * @param userListenProcessVo
  * @return
  */
-void updateListenProcess(Long userId, UserListenProcessVo userListenProcessVo);
+void updateUserListenProcess(Long userId, UserListenProcessVo userListenProcessVo);
 ```
 
 **UserListenProcessServiceImpl**实现类:
@@ -865,55 +866,62 @@ private RedisTemplate redisTemplate;
 private RabbitService rabbitService;
 
 /**
- * 保存更新声音播放进度
+ * 更新声音播放进度
  *
  * @param userListenProcessVo
  * @return
  */
 @Override
-public void updateListenProcess(Long userId, UserListenProcessVo userListenProcessVo) {
-    //1.查询当前用户某个声音播放进度
-    //1.1 构建查询条件
-    Query query = new Query();
-    query.addCriteria(Criteria.where("userId").is(userId).and("trackId").is(userListenProcessVo.getTrackId()));
-    //1.2 执行查询:注意每个用户都有自己播放进度集合
-    String collectionName = MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_LISTEN_PROCESS, userId);
-    UserListenProcess listenProcess = mongoTemplate.findOne(query, UserListenProcess.class, collectionName);
-    //2.如果不存在构建声音播放进度保存到MongoDB中
-    BigDecimal breakSecond = userListenProcessVo.getBreakSecond().setScale(0, RoundingMode.HALF_UP);
-    if (listenProcess == null) {
-        listenProcess = new UserListenProcess();
-        listenProcess.setUserId(userId);
-        listenProcess.setTrackId(userListenProcessVo.getTrackId());
-        listenProcess.setAlbumId(userListenProcessVo.getAlbumId());
-        listenProcess.setBreakSecond(breakSecond);
-        listenProcess.setCreateTime(new Date());
-        listenProcess.setUpdateTime(new Date());
+public void updateUserListenProcess(Long userId, UserListenProcessVo userListenProcessVo) {
+    //1.获取用户播放进度集合名称
+    String collectionName = this.getCollectionName(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.如果播放进度不存在,构建播放进度,保存到MongoDB
+    //设置播放秒数小数位,舍入模式
+    userListenProcessVo.setBreakSecond(userListenProcessVo.getBreakSecond().setScale(0, RoundingMode.DOWN));
+    if (userListenProcess == null) {
+        userListenProcess = new UserListenProcess();
+        userListenProcess.setUserId(userId);
+        userListenProcess.setAlbumId(userListenProcessVo.getAlbumId());
+        userListenProcess.setTrackId(userListenProcessVo.getTrackId());
+        userListenProcess.setBreakSecond(userListenProcessVo.getBreakSecond());
+        userListenProcess.setCreateTime(new Date());
+        userListenProcess.setUpdateTime(new Date());
+        //mongoTemplate.save(userListenProcess, collectionName);
     } else {
-        //3.如果已存在播放进度,更新播放进度时间及更新时间 包含文档注解ID
-        listenProcess.setBreakSecond(breakSecond);
-        listenProcess.setUpdateTime(new Date());
+        //4.如果播放进度存在,更新播放秒+更新时间
+        userListenProcess.setBreakSecond(userListenProcessVo.getBreakSecond());
+        userListenProcess.setUpdateTime(new Date());
+        //mongoTemplate.save(userListenProcess, collectionName);
     }
-    mongoTemplate.save(listenProcess, collectionName);
-
-    //4.采用MQ异步更新数据库及索引库中统计信息(播放量)
-
-    //4.1 确保某个用户当日内只能更新一次播放统计量-生产者幂等性(由于播放进度更新会定时调用)
-    //4.1.1 构建生产者幂等性Key 形式:前缀:userId_albumId_trackId
-    String key = RedisConstant.USER_TRACK_REPEAT_STAT_PREFIX + userId + "_" + userListenProcessVo.getAlbumId() + "_" + userListenProcessVo.getTrackId();
-    //4.1.2 计算过期时间 要求:当前日内一次统计有效
-    long ttl = DateUtil.endOfDay(new Date()).getTime() - System.currentTimeMillis();
-    Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "", ttl, TimeUnit.MILLISECONDS);
-    //4.2 发送更新声音统计VO消息到RabbitMQ
+    mongoTemplate.save(userListenProcess, collectionName);
+
+    //5.TODO 基于MQ可靠性消息,更新声音/专辑(播放量)在MySQL以及ES中统计数值
+    //5.1 当天内判断当前用户是否为第一次收听更新统计数值 核心:生产者生产消息幂等性
+    //5.1.1 关键点找出业务标识作为RedisKey:前缀:用户ID_专辑ID_声音ID
+    String redisKey
+            = RedisConstant.USER_TRACK_REPEAT_STAT_PREFIX + userId + "_" + userListenProcessVo.getAlbumId() + "_" + userListenProcessVo.getTrackId();
+    //5.1.2 采用Redis提供set k v ex nx  命令,设置RedisKey,设置过期时间,写入成功则发送MQ消息,更新统计数值;写入失败则忽略
+    Date date = new Date();
+    long ttl = DateUtil.endOfDay(date).getTime() - date.getTime();
+    Boolean flag = redisTemplate.opsForValue().setIfAbsent(redisKey, "1", ttl, TimeUnit.MILLISECONDS);
+    //5.2 发送MQ消息,广播消息(通知"专辑服务"更新DB,通知"搜索服务"更新ES)
     if (flag) {
-        //4.2.1 创建更新统“计消息VO对象
+        //5.2.1 构建MQ消息VO对象 包含:专辑ID、声音ID、统计类型、增量数值、businessNo-UUID(用于消费者端幂等性处理),TODO VO必须序列化接口
         TrackStatMqVo mqVo = new TrackStatMqVo();
+        mqVo.setBusinessNo(IdUtil.fastUUID());
         mqVo.setAlbumId(userListenProcessVo.getAlbumId());
         mqVo.setTrackId(userListenProcessVo.getTrackId());
         mqVo.setStatType(SystemConstant.TRACK_STAT_PLAY);
         mqVo.setCount(1);
-        mqVo.setBusinessNo(IdUtil.randomUUID());
-        //4.2.2 发送MQ消息
+        //5.2.2 发送MQ消息
         rabbitService.sendMessage(MqConst.EXCHANGE_TRACK, MqConst.ROUTING_TRACK_STAT_UPDATE, mqVo);
     }
 }
@@ -921,14 +929,14 @@ public void updateListenProcess(Long userId, UserListenProcessVo userListenProce
 
 注意:要修改**TrackStatMqVo**实现序列化接口,否则会导致发送MQ消息失败
 
-![image-20240814161739086](assets/image-20240814161739086.png)
+ ![image-20240814161739086](assets/image-20240814161739086.png)
 
 ### 3.2.2 更新MySQL统计信息
 
 在`service-album` 微服务中添加监听消息:
 
 ```java
-package com.atguigu.tingshu.album;
+package com.atguigu.tingshu.album.receiver;
 
 import com.atguigu.tingshu.album.service.TrackInfoService;
 import com.atguigu.tingshu.common.constant.RedisConstant;
@@ -946,12 +954,11 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Component;
 
-import java.io.IOException;
 import java.util.concurrent.TimeUnit;
 
 /**
  * @author: atguigu
- * @create: 2024-08-14 14:23
+ * @create: 2025-06-09 14:30
  */
 @Slf4j
 @Component
@@ -963,13 +970,12 @@ public class AlbumReceiver {
     @Autowired
     private TrackInfoService trackInfoService;
 
-
     /**
-     * 监听到更新声音统计消息,更新MySQL中统计数值
+     * 监听到更新声音统计数值MQ消息
      *
      * @param mqVo
-     * @param message
      * @param channel
+     * @param message
      */
     @SneakyThrows
     @RabbitListener(bindings = @QueueBinding(
@@ -977,27 +983,26 @@ public class AlbumReceiver {
             value = @Queue(value = MqConst.QUEUE_TRACK_STAT_UPDATE, durable = "true"),
             key = MqConst.ROUTING_TRACK_STAT_UPDATE
     ))
-    public void updateStat(TrackStatMqVo mqVo, Message message, Channel channel) {
-        String key = "";
-        try {
-            if (mqVo != null) {
-                log.info("监听到更新声音统计消息:{}", mqVo);
-                //1. 处理消费者幂等性问题(一个消息被多次消费带来数据不一致问题)
-                //1.1 基于MQ消息对象中唯一标识作为Redis中Key 采用set k v ex nx命令写入Redis
-                key = RedisConstant.BUSINESS_PREFIX + mqVo.getBusinessNo();
-                Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "", 10, TimeUnit.MINUTES);
-                if (flag) {
-                    //2. 更新统计数值 业务处理可能抛出异常
-                    trackInfoService.updateStat(mqVo);
+    public void updateTrackStat(TrackStatMqVo mqVo, Channel channel, Message message) {
+        if (mqVo != null) {
+            log.info("更新声音统计:{}", mqVo);
+            //1.先对消费者进行幂等性处理
+            //1.1 找出业务消息的标识 作为setnx中key
+            String redisKey = RedisConstant.USER_TRACK_REPEAT_STAT_PREFIX + "db:" + mqVo.getBusinessNo();
+            //1.2 尝试存入Redis
+            Boolean flag = redisTemplate.opsForValue().setIfAbsent(redisKey, "1", 10, TimeUnit.MINUTES);
+            if (flag) {
+                try {
+                    //2.更新声音/专辑统计数值
+                    trackInfoService.updateTrackStat(mqVo);
+                } catch (Exception e) {
+                    redisTemplate.delete(redisKey);
+                    //如果异常将无法处理消息发送到死信(异常)交换机->死信队列->消费者处理进入存入消费者异常消息表->人工处理
+                    channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
                 }
             }
-            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
-        } catch (Exception e) {
-            log.error("[专辑服务]监听更新声音统计消息异常:{}", e);
-            //3.捕获到业务处理异常后,将消息再次进行入队,RabbitMQ再次投递消息
-            redisTemplate.delete(key);
-            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
         }
+        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
     }
 }
 ```
@@ -1006,10 +1011,10 @@ public class AlbumReceiver {
 
 ```java
 /**
- * 更新声音统计数值
+ * 更新DB中声音/专辑统计数值
  * @param mqVo
  */
-void updateStat(TrackStatMqVo mqVo);
+void updateTrackStat(TrackStatMqVo mqVo);
 ```
 
 在**TrackInfoServiceImpl** 中添加实现
@@ -1019,35 +1024,42 @@ void updateStat(TrackStatMqVo mqVo);
 private AlbumStatMapper albumStatMapper;
 
 /**
- * 更新声音统计数值
- * 注意:如果声音被播放,被评论 所属专辑也需要更新统计数值
+ * 更新DB中声音/专辑统计数值
  *
  * @param mqVo
  */
 @Override
 @Transactional(rollbackFor = Exception.class)
-public void updateStat(TrackStatMqVo mqVo) {
+public void updateTrackStat(TrackStatMqVo mqVo) {
     //1.更新声音统计数值
-    LambdaUpdateWrapper<TrackStat> updateWrapper = new LambdaUpdateWrapper<>();
-    updateWrapper.eq(TrackStat::getTrackId, mqVo.getTrackId());
-    updateWrapper.eq(TrackStat::getStatType, mqVo.getStatType());
-    updateWrapper.setSql("stat_num=stat_num+" + mqVo.getCount());
-    trackStatMapper.update(null, updateWrapper);
-    //2.如果统计类型是:声音播放 所属专辑统计信息也一并修改
-    if (SystemConstant.TRACK_STAT_PLAY.equals(mqVo.getStatType())) {
-        LambdaUpdateWrapper<AlbumStat> albumStatLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
-        albumStatLambdaUpdateWrapper.eq(AlbumStat::getAlbumId, mqVo.getAlbumId());
-        albumStatLambdaUpdateWrapper.eq(AlbumStat::getStatType, SystemConstant.ALBUM_STAT_PLAY);
-        albumStatLambdaUpdateWrapper.setSql("stat_num = stat_num+" + mqVo.getCount());
-        albumStatMapper.update(null, albumStatLambdaUpdateWrapper);
+    trackStatMapper.update(
+            null,
+            new LambdaUpdateWrapper<TrackStat>()
+                    .eq(TrackStat::getTrackId, mqVo.getTrackId())
+                    .eq(TrackStat::getStatType, mqVo.getStatType())
+                    .setSql("stat_num = stat_num + " + mqVo.getCount())
+    );
+
+    //2.如果是"播放"or"评论"还需要更新专辑统计数值
+    if (TRACK_STAT_PLAY.equals(mqVo.getStatType())) {
+        //更新专辑统计表播放量
+        albumStatMapper.update(
+                null,
+                new LambdaUpdateWrapper<AlbumStat>()
+                        .eq(AlbumStat::getAlbumId, mqVo.getAlbumId())
+                        .eq(AlbumStat::getStatType, ALBUM_STAT_PLAY)
+                        .setSql("stat_num = stat_num + " + mqVo.getCount())
+        );
     }
-    //3.如果统计类型是:声音评论 所属专辑统计信息也一并修改
-    if (SystemConstant.TRACK_STAT_COMMENT.equals(mqVo.getStatType())) {
-        LambdaUpdateWrapper<AlbumStat> albumStatLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
-        albumStatLambdaUpdateWrapper.eq(AlbumStat::getAlbumId, mqVo.getAlbumId());
-        albumStatLambdaUpdateWrapper.eq(AlbumStat::getStatType, SystemConstant.ALBUM_STAT_COMMENT);
-        albumStatLambdaUpdateWrapper.setSql("stat_num = stat_num+" + mqVo.getCount());
-        albumStatMapper.update(null, albumStatLambdaUpdateWrapper);
+    if (TRACK_STAT_COMMENT.equals(mqVo.getStatType())) {
+        //更新专辑统计表评论量
+        albumStatMapper.update(
+                null,
+                new LambdaUpdateWrapper<AlbumStat>()
+                        .eq(AlbumStat::getAlbumId, mqVo.getAlbumId())
+                        .eq(AlbumStat::getStatType, ALBUM_STAT_COMMENT)
+                        .setSql("stat_num = stat_num + " + mqVo.getCount())
+        );
     }
 }
 ```
@@ -1248,11 +1260,10 @@ public Result<List<BaseCategory1>> getAllCategory1() {
 
 ```java
 /**
- * 更新不同分类不同排行TOP20数据
- *
+ * 更新Redis小时榜
  * @return
  */
-@Operation(summary = "更新不同分类不同排行TOP20数据")
+@Operation(summary = "更新Redis小时榜")
 @GetMapping("/albumInfo/updateLatelyAlbumRanking")
 public Result updateLatelyAlbumRanking() {
     searchService.updateLatelyAlbumRanking();
@@ -1277,57 +1288,50 @@ void updateLatelyAlbumRanking();
 private RedisTemplate redisTemplate;
 
 /**
- * 更新不同分类不同排行TOP20数据
+ * 更新Redis小时榜
  *
  * @return
  */
 @Override
 public void updateLatelyAlbumRanking() {
     try {
-        //1.远程调用专辑服务获取所有1级分类获取一级分类ID列表
+        //1.检索ES得到响应的TOP20专辑列表
+        //1.1 远程调用专辑服务获取一级分类ID列表
         List<BaseCategory1> baseCategory1List = albumFeignClient.getAllCategory1().getData();
-        if (CollectionUtil.isNotEmpty(baseCategory1List)) {
-            //2.遍历所有一级分类ID-根据分类ID+排序方式+限制返回文档数量
-            List<Long> category1IdList = baseCategory1List.stream().map(BaseCategory1::getId).collect(Collectors.toList());
-
-            for (Long category1Id : category1IdList) {
-
-                //声明当前分类hash结构Key
-                String key = RedisConstant.RANKING_KEY_PREFIX + category1Id;
-                //处理某个一级分类排行数据,遍历5种不同排序字段
-                String[] rankingDimensionArray = new String[]{"hotScore", "playStatNum", "subscribeStatNum", "buyStatNum", "commentStatNum"};
-                for (String rankingDimension : rankingDimensionArray) {
-                    SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(
-                            s -> s.index(INDE_NAME)
-                                    .query(q -> q.term(t -> t.field("category1Id").value(category1Id)))
-                                    .sort(sort -> sort.field(f -> f.field(rankingDimension).order(SortOrder.Desc)))
-                                    .size(20)
-                                    .source(s1 -> s1.filter(f -> f.includes("id", "albumTitle", "albumIntro", "coverUrl", "includeTrackCount", "playStatNum", "createTime", "payType"))),
-                            AlbumInfoIndex.class
-                    );
-                    //3.解析当前分类下当前排序方式命中TOP20专辑文档列表
-                    List<Hit<AlbumInfoIndex>> hitList = searchResponse.hits().hits();
-                    if (CollectionUtil.isNotEmpty(hitList)) {
-                        //3.1 遍历命中记录对象得到Hit对象中中_source(专辑对象)
-                        List<AlbumInfoIndexVo> top20List = hitList.stream()
-                                .map(hit -> {
-                                    AlbumInfoIndex albumInfoIndex = hit.source();
-                                    //3.2 处理高亮
-                                    Map<String, List<String>> highlightMap = hit.highlight();
-                                    if (CollectionUtil.isNotEmpty(highlightMap)) {
-                                        String highlightText = highlightMap.get("albumTitle").get(0);
-                                        albumInfoIndex.setAlbumTitle(highlightText);
-                                    }
-                                    return BeanUtil.copyProperties(albumInfoIndex, AlbumInfoIndexVo.class);
-                                }).collect(Collectors.toList());
-                        //4.将不同分类不同维度的TOP20数据放入Redis
-                        redisTemplate.opsForHash().put(key, rankingDimension, top20List);
-                    }
+        Assert.notNull(baseCategory1List, "一级分类列表为空!");
+        List<Long> category1IdList = baseCategory1List
+                .stream()
+                .map(BaseCategory1::getId)
+                .collect(Collectors.toList());
+
+        //1.2 循环遍历1级分类ID-构建DSL检索 条件:一级分类ID(共计15个)+排序(五种排序方式)+长度(前20)
+        for (Long category1Id : category1IdList) {
+            //定义Redis中小时榜hash的Key
+            String redisKey = RedisConstant.RANKING_KEY_PREFIX + category1Id;
+            //创建绑定hash对象方便操作hash结构
+            BoundHashOperations<String, String, List<AlbumInfoIndex>> hashOps = redisTemplate.boundHashOps(redisKey);
+            //处理某个一级分类排行数据,遍历5种不同排序字段
+            String[] rankingDimensionArray
+                    = new String[]{"hotScore", "playStatNum", "subscribeStatNum", "buyStatNum", "commentStatNum"};
+            for (String rankingDimension : rankingDimensionArray) {
+                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)))
+                        , AlbumInfoIndex.class);
+                //2.解析ES检索结果,将专辑列表保存到Redis中Hash中
+                List<Hit<AlbumInfoIndex>> hitList = searchResponse.hits().hits();
+                if (CollUtil.isNotEmpty(hitList)) {
+                    List<AlbumInfoIndex> top20List = hitList.stream().map(hit -> {
+                        AlbumInfoIndex source = hit.source();
+                        return source;
+                    }).collect(Collectors.toList());
+                    hashOps.put(rankingDimension, top20List);
                 }
             }
         }
-    } catch (Exception e) {
-        log.error("[搜索服务]更新排行榜Redis异常:{}", e);
+    } catch (IOException e) {
+        log.error("更新Redis小时榜失败!");
         throw new RuntimeException(e);
     }
 }
@@ -1370,7 +1374,7 @@ public Result<List<AlbumInfoIndexVo>> getRankingList(@PathVariable Long category
 
 ```java
 /**
- * 获取不同分类下不同排行维度TOP20
+ * 查询Redis中不同分类下不同维度TOP20专辑列表
  * @param category1Id
  * @param dimension
  * @return
@@ -1382,7 +1386,7 @@ List<AlbumInfoIndexVo> getRankingList(Long category1Id, String dimension);
 
 ```java
 /**
- * 获取不同分类下不同排行维度TOP20
+ * 查询Redis中不同分类下不同维度TOP20专辑列表
  *
  * @param category1Id
  * @param dimension
@@ -1390,13 +1394,21 @@ List<AlbumInfoIndexVo> getRankingList(Long category1Id, String dimension);
  */
 @Override
 public List<AlbumInfoIndexVo> getRankingList(Long category1Id, String dimension) {
-    //方式一:根据key+filed获取hash的VALUE
-    String key = RedisConstant.RANKING_KEY_PREFIX + category1Id;
-    //List<AlbumInfoIndexVo> list = (List<AlbumInfoIndexVo>) redisTemplate.opsForHash().get(key, dimension);
-    //方式二:创建操作hash对象
-    BoundHashOperations<String, String, List<AlbumInfoIndexVo>> boundHashOperations = redisTemplate.boundHashOps(key);
-    List<AlbumInfoIndexVo> list = boundHashOperations.get(dimension);
-    return list;
+    //定义Redis中小时榜hash的Key
+    String redisKey = RedisConstant.RANKING_KEY_PREFIX + category1Id;
+    String field = dimension;
+    //创建绑定hash对象方便操作hash结构
+    BoundHashOperations<String, String, List<AlbumInfoIndex>> hashOps = redisTemplate.boundHashOps(redisKey);
+    Boolean flag = hashOps.hasKey(field);
+    if (flag) {
+        List<AlbumInfoIndex> list = hashOps.get(field);
+        List<AlbumInfoIndexVo> albumInfoIndexVoList = list
+                .stream()
+                .map(albumInfoIndex -> BeanUtil.copyProperties(albumInfoIndex, AlbumInfoIndexVo.class))
+                .collect(Collectors.toList());
+        return albumInfoIndexVoList;
+    }
+    return null;
 }
 ```