第5章 详情介绍.md 48 KB

谷粒随享

第5章 专辑/声音详情

学习目标:

  • 专辑详情业务需求
    • 专辑服务 1.专辑信息 2.分类信息 3.统计信息 4,主播信息
    • 搜索服务:汇总专辑详情数据
  • 专辑包含声音列表(付费标识动态展示)
  • MongoDB文档型数据库应用
  • 基于MongoDB存储用户对于声音播放进度
  • 基于Redis实现排行榜(将不同不同分类下包含各个维度热门专辑排行)

1、专辑详情

专辑详情页面渲染需要以下四项数据:

  • albumInfo:当前专辑信息
  • albumStatVo:专辑统计信息
  • baseCategoryView:专辑分类信息
  • announcer:专辑主播信息

因此接下来,我们需要在专辑微服务用户微服务中补充RestFul接口实现 并且 提供远程调用Feign API接口给搜索微服务来调用获取。

在专辑搜索微服务中编写控制器汇总专辑详情所需数据

以下是详情需要获取到的数据集

  1. 通过专辑Id 获取专辑数据{已存在}
  2. 通过专辑Id 获取专辑统计信息{不存在}
  3. 通过三级分类Id 获取到分类数据{已存在}
  4. 通过用户Id 获取到主播信息{存在}

1.1 服务提供方提供接口

1.1.1 根据专辑Id 获取专辑数据(已完成)

1.1.2 根据三级分类Id获取到分类信息(已完成)

1.1.3 根据用户Id 获取主播信息(已完成)

1.1.4 根据专辑Id 获取统计信息

YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/67

AlbumInfoApiController 控制器

/**
 * 根据专辑ID查询专辑统计信息
 *
 * @param albumId 专辑ID
 * @return
 */
@Operation(summary = "根据专辑ID查询专辑统计信息")
@GetMapping("/albumInfo/getAlbumStatVo/{albumId}")
public Result<AlbumStatVo> getAlbumStatVo(@PathVariable Long albumId) {
    AlbumStatVo albumStatVo = albumInfoService.getAlbumStatVo(albumId);
    return Result.ok(albumStatVo);
}

AlbumInfoService接口

/**
 * 根据专辑ID查询专辑统计信息
 *
 * @param albumId 专辑ID
 * @return
 */
AlbumStatVo getAlbumStatVo(Long albumId);

AlbumInfoServiceImpl实现类

/**
 * 根据专辑ID查询专辑统计信息
 *
 * @param albumId 专辑ID
 * @return
 */
@Override
public AlbumStatVo getAlbumStatVo(Long albumId) {
    return albumInfoMapper.getAlbumStatVo(albumId);
}

albumInfoMapper.java

/**
 * 根据专辑ID查询专辑统计信息
 *
 * @param albumId 专辑ID
 * @return
 */
AlbumStatVo getAlbumStatVo(@Param("albumId") Long albumId);

albumInfoMapper.xml

<!--根据专辑ID查询专辑统计信息-->
<select id="getAlbumStatVo" resultType="com.atguigu.tingshu.vo.album.AlbumStatVo">
    select
        album_id,
        max(if(stat_type='0401', stat_num, 0)) playStatNum,
        max(if(stat_type='0402', stat_num, 0)) subscribeStatNum,
        max(if(stat_type='0403', stat_num, 0)) buyStatNum,
        max(if(stat_type='0404', stat_num, 0)) commentStatNum
    from album_stat where album_id = #{albumId} and is_deleted = 0
    group by album_id
</select>

service-album-client模块AlbumFeignClient 接口中添加

/**
 * 根据专辑ID查询专辑统计信息
 *
 * @param albumId 专辑ID
 * @return
 */
@GetMapping("/albumInfo/getAlbumStatVo/{albumId}")
public Result<AlbumStatVo> getAlbumStatVo(@PathVariable Long albumId);

AlbumDegradeFeignClient熔断类:

@Override
public Result<AlbumStatVo> getAlbumStatVo(Long albumId) {
    log.error("[专辑模块]提供远程调用getAlbumStatVo服务降级");
    return null;
}

1.2 服务调用方汇总数据

回显时,后台需要提供将数据封装到map集合中;

result.put("albumInfo", albumInfo);			获取专辑信息
result.put("albumStatVo", albumStatVo);		获取专辑统计信息
result.put("baseCategoryView", baseCategoryView);	获取分类信息
result.put("announcer", userInfoVo);	获取主播信息

YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/69

service-search 微服务itemApiController 控制器中添加

package com.atguigu.tingshu.search.api;

import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.search.service.ItemService;
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.Map;

@Tag(name = "专辑详情管理")
@RestController
@RequestMapping("api/search")
@SuppressWarnings({"all"})
public class itemApiController {

	@Autowired
	private ItemService itemService;


	/**
	 * 用于小程序专辑详情页渲染-根据专辑ID查询专辑详情
	 * @param albumId 专辑ID
	 * @return
	 */
	@Operation(summary = "用于小程序专辑详情页渲染-根据专辑ID查询专辑详情")
	@GetMapping("/albumInfo/{albumId}")
	public Result<Map<String, Object>> getAlbumItem(@PathVariable Long albumId){
		Map<String, Object> mapResult = itemService.getAlbumItem(albumId);
		return Result.ok(mapResult);
	}

}

接口与实现

package com.atguigu.tingshu.search.service;

import java.util.Map;

public interface ItemService {

    /**
     * 用于小程序专辑详情页渲染-根据专辑ID查询专辑详情
     * @param albumId 专辑ID
     * @return
     */
    Map<String, Object> getAlbumItem(Long albumId);
}
package com.atguigu.tingshu.search.service.impl;

import cn.hutool.core.lang.Assert;
import com.atguigu.tingshu.album.AlbumFeignClient;
import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.BaseCategoryView;
import com.atguigu.tingshu.search.service.ItemService;
import com.atguigu.tingshu.user.client.UserFeignClient;
import com.atguigu.tingshu.vo.album.AlbumStatVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadPoolExecutor;

@Slf4j
@Service
@SuppressWarnings({"all"})
public class ItemServiceImpl implements ItemService {


    @Autowired
    private AlbumFeignClient albumFeignClient;

    @Autowired
    private UserFeignClient userFeignClient;

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;


    /**
     * 用于小程序专辑详情页渲染-根据专辑ID查询专辑详情
     * - albumInfo:当前专辑信息
     * - albumStatVo:专辑统计信息
     * - baseCategoryView:专辑分类信息
     * - announcer:专辑主播信息
     *
     * @param albumId 专辑ID
     * @return
     */
    @Override
    public Map<String, Object> getAlbumItem(Long albumId) {
        //1.创建渲染结果Map 修改为异步任务多线程后存在多线程并发写hashMap,导致key被覆盖或者死循环
        Map<String, Object> mapResult = new ConcurrentHashMap<>();
        //2.远程调用专辑服务-获取专辑基本信息
        CompletableFuture<AlbumInfo> albumInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            AlbumInfo albumInfo = albumFeignClient.getAlbumInfo(albumId).getData();
            Assert.notNull(albumInfo, "专辑信息为空!");
            mapResult.put("albumInfo", albumInfo);
            return albumInfo;
        }, threadPoolExecutor);

        //3.远程调用专辑服务-获取专辑所属分类信息
        CompletableFuture<Void> baseCategoryViewCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            BaseCategoryView baseCategoryView = albumFeignClient.getCategoryView(albumInfo.getCategory3Id()).getData();
            Assert.notNull(baseCategoryView, "分类为空!");
            mapResult.put("baseCategoryView", baseCategoryView);
        }, threadPoolExecutor);

        //4.远程调用专辑服务-获取专辑统计信息
        CompletableFuture<Void> albumStatVoCompletableFuture = CompletableFuture.runAsync(() -> {
            AlbumStatVo albumStatVo = albumFeignClient.getAlbumStatVo(albumId).getData();
            Assert.notNull(albumStatVo, "专辑统计为空!");
            mapResult.put("albumStatVo", albumStatVo);
        }, threadPoolExecutor);

        //5.远程调用用户服务-获取主播信息
        CompletableFuture<Void> announcerCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            UserInfoVo userInfoVo = userFeignClient.getUserInfoVoById(albumInfo.getUserId()).getData();
            Assert.notNull(userInfoVo, "主播信息为空!");
            mapResult.put("announcer", userInfoVo);
        }, threadPoolExecutor);

        //6.组装异步任务
        CompletableFuture.allOf(
                albumInfoCompletableFuture,
                baseCategoryViewCompletableFuture,
                announcerCompletableFuture,
                albumStatVoCompletableFuture
        ).join();

        return mapResult;
    }
}

1.3 获取专辑声音列表

需求:根据专辑ID分页查询声音列表,返回当前页10条记录,对每条声音付费标识处理。关键点:哪个声音需要展示付费标识。

默认每个声音付费标识为:false

判断专辑付费类型:0101-免费、0102-vip免费、0103-付费

  • 用户未登录
    • 专辑类型不是免费,将除了免费可以试听声音外,将本页中其余声音付费标识设置:true
  • 用户登录(获取是否为VIP)
    • 不是VIP,或者VIP过期(除了免费以外声音全部设置为付费)
    • 是VIP,专辑类型为付费 需要进行处理
  • 统一处理需要付费情况
    • 获取用户购买情况(专辑购买,或者声音购买)得到每个声音购买状态
    • 判断根据用户购买情况设置声音付费标识

1.3.1 获取用户声音列表付费情况

YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/87

user_paid_album 这张表记录了用户购买过的专辑

user_paid_track 这张表记录了用户购买过的声音

如果购买过,则在map 中存储数据 key=trackId value = 1 未购买value则返回0

例如:

  • 某专辑第一页,除了试听的声音(前五)从6-10个声音需要在用户微服务中判断5个声音是否购买过

  • 用户翻到第二页,从11-20个声音同样需要判断用户购买情况

UserInfoApiController 控制器:

/**
 * 专辑下声音某一页中需要验证专辑或声音购买状态
 *
 * @param userId               用户ID
 * @param albumId              专辑ID
 * @param needCheckTrackIdList 需要检查声音购买状态ID集合
 * @return Map<声音ID , 购买状态>
 */
@Operation(summary = "专辑下声音某一页中需要验证专辑或声音购买状态")
@PostMapping("/userInfo/userIsPaidTrack/{userId}/{albumId}")
public Result<Map<Long, Integer>> userIsPaidTrack(@PathVariable Long userId, @PathVariable Long albumId, @RequestBody List<Long> needCheckTrackIdList) {
    Map<Long, Integer> buyStatusMap = userInfoService.userIsPaidTrack(userId, albumId, needCheckTrackIdList);
    return Result.ok(buyStatusMap);
}

UserInfoService接口

/**
 * 专辑下声音某一页中需要验证专辑或声音购买状态
 *
 * @param userId               用户ID
 * @param albumId              专辑ID
 * @param needCheckTrackIdList 需要检查声音购买状态ID集合
 * @return Map<声音ID , 购买状态>
 */
Map<Long, Integer> userIsPaidTrack(Long userId, Long albumId, List<Long> needCheckTrackIdList);

UserInfoServiceImpl实现类

@Autowired
private UserPaidAlbumMapper userPaidAlbumMapper;

@Autowired
private UserPaidTrackMapper userPaidTrackMapper;


/**
 * 专辑下声音某一页中需要验证专辑或声音购买状态
 *
 * @param userId               用户ID
 * @param albumId              专辑ID
 * @param needCheckTrackIdList 需要检查声音购买状态ID集合(专辑下某一页声音ID列表)
 * @return Map<声音ID , 购买状态>
 */
@Override
public Map<Long, Integer> userIsPaidTrack(Long userId, Long albumId, List<Long> needCheckTrackIdList) {
    //1.根据用户ID+专辑ID查询专辑购买记录
    LambdaQueryWrapper<UserPaidAlbum> userPaidAlbumLambdaQueryWrapper = new LambdaQueryWrapper<>();
    userPaidAlbumLambdaQueryWrapper.eq(UserPaidAlbum::getUserId, userId);
    userPaidAlbumLambdaQueryWrapper.eq(UserPaidAlbum::getAlbumId, albumId);
    Long count = userPaidAlbumMapper.selectCount(userPaidAlbumLambdaQueryWrapper);
    if (count > 0) {
        //2 如果用户购买该专辑则将 needCheckTrackIdList 购买状态:1
        Map<Long, Integer> buyStatusMap = new HashMap<>();
        for (Long trackId : needCheckTrackIdList) {
            buyStatusMap.put(trackId, 1);
        }
        return buyStatusMap;
    }
    //3. 未购买该专辑,继续查询用户购买声音记录
    LambdaQueryWrapper<UserPaidTrack> userPaidTrackLambdaQueryWrapper = new LambdaQueryWrapper<>();
    userPaidTrackLambdaQueryWrapper.eq(UserPaidTrack::getUserId, userId);
    userPaidTrackLambdaQueryWrapper.in(UserPaidTrack::getTrackId, needCheckTrackIdList);
    //3.1 用于已购声音记录
    List<UserPaidTrack> userPaidTrackList = userPaidTrackMapper.selectList(userPaidTrackLambdaQueryWrapper);
    //3.2 判断已购声音集合如果为空 将待检查声音ID购买状态:0
    if (CollectionUtil.isEmpty(userPaidTrackList)) {
        Map<Long, Integer> buyStatusMap = new HashMap<>();
        for (Long trackId : needCheckTrackIdList) {
            buyStatusMap.put(trackId, 0);
        }
        return buyStatusMap;
    }
    //3.3 待检查的声音ID中用户有购买,确定哪些是已购买 哪些是未购买
    List<Long> userPaidTrackIdList = userPaidTrackList.stream().map(UserPaidTrack::getTrackId).collect(Collectors.toList());
    Map<Long, Integer> buyStatusMap = new HashMap<>();
    for (Long needCheckTrackId : needCheckTrackIdList) {
        if (userPaidTrackIdList.contains(needCheckTrackId)) {
            //该声音已被用户购买
            buyStatusMap.put(needCheckTrackId, 1);
        } else {
            //该声音未被用户购买
            buyStatusMap.put(needCheckTrackId, 0);
        }
    }
    return buyStatusMap;
}

service-user-client模块中UserFeignClient 远程调用接口中添加:

/**
 * 专辑下声音某一页中需要验证专辑或声音购买状态
 *
 * @param userId               用户ID
 * @param albumId              专辑ID
 * @param needCheckTrackIdList 需要检查声音购买状态ID集合
 * @return Map<声音ID , 购买状态>
 */
@PostMapping("/userInfo/userIsPaidTrack/{userId}/{albumId}")
public Result<Map<Long, Integer>> userIsPaidTrack(@PathVariable Long userId, @PathVariable Long albumId, @RequestBody List<Long> needCheckTrackIdList);

UserDegradeFeignClient熔断类

@Component
public class UserDegradeFeignClient implements UserInfoFeignClient {

    @Override
    public Result<Map<Long, Integer>> userIsPaidTrack(Long userId, Long albumId, List<Long> needCheckTrackIdList) {
        log.error("[用户服务]提供远程调用userIsPaidTrack服务降级");
        return null;
    }
    
}

1.3.2 查询专辑声音列表

service-album 微服务中添加控制器. 获取专辑声音列表时,我们将数据都统一封装到AlbumTrackListVo实体类中

YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/89

TrackInfoApiController控制器

/**
 * 用于小程序端专辑页面展示分页声音列表,动态根据用户展示声音付费标识
 *
 * @param albumId
 * @param page
 * @param limit
 * @return
 */
@GuiGuLogin(required = false)
@Operation(summary = "用于小程序端专辑页面展示分页声音列表,动态根据用户展示声音付费标识")
@GetMapping("/trackInfo/findAlbumTrackPage/{albumId}/{page}/{limit}")
public Result<Page<AlbumTrackListVo>> getAlbumTrackPage(@PathVariable Long albumId, @PathVariable Integer page, @PathVariable Integer limit) {
    //1.获取用户ID
    Long userId = AuthContextHolder.getUserId();
    //2.封装分页对象
    Page<AlbumTrackListVo> pageInfo = new Page<>(page, limit);
    //3.调用业务层封装分页对象
    pageInfo = trackInfoService.getAlbumTrackPage(pageInfo, albumId, userId);
    return Result.ok(pageInfo);
}

TrackInfoService接口:

/**
 * 用于小程序端专辑页面展示分页声音列表,动态根据用户展示声音付费标识
 *
 * @param pageInfo MP分页对象
 * @param albumId 专辑ID
 * @param userId 用户ID
 * @return
 */
Page<AlbumTrackListVo> getAlbumTrackPage(Page<AlbumTrackListVo> pageInfo, Long albumId, Long userId);

TrackInfoServiceImpl实现类:

  • 根据专辑Id 获取到专辑列表,
    • 用户为空的时候,然后找出哪些是需要付费的声音并显示付费 isShowPaidMark=true

付费类型: 0101-免费 0102-vip付费 0103-付费

  • ​ 用户不为空的时候
    • 判断用户的类型
    • vip 免费类型
    • 如果不是vip 需要付费
    • 如果是vip 但是已经过期了 也需要付费
    • 需要付费
    • 统一处理需要付费业务

​ 获取到声音Id列表集合 与 用户购买声音Id集合进行比较 将用户购买的声音存储到map中,key=trackId value = 1或0; 1:表示购买过,0:表示没有购买过

如果声音列表不包含,则将显示为付费,否则判断用户是否购买过声音,没有购买过设置为付费

@Autowired
private UserFeignClient userFeignClient;

/**
 * 用于小程序端专辑页面展示分页声音列表,动态根据用户展示声音付费标识
 *
 * @param pageInfo MP分页对象
 * @param albumId  专辑ID
 * @param userId   用户ID
 * @return
 */
@Override
public Page<AlbumTrackListVo> getAlbumTrackPage(Page<AlbumTrackListVo> pageInfo, Long albumId, Long userId) {
    //1.根据专辑ID查询该专辑下包含声音列表-默认查询到声音:不需要付费
    pageInfo = trackInfoMapper.getAlbumTrackPage(pageInfo, albumId);
    //1.1 获取到当前页中声音列表
    List<AlbumTrackListVo> trackList = pageInfo.getRecords();
    //1.2 根据专辑ID查询专辑信息
    AlbumInfo albumInfo = albumInfoMapper.selectById(albumId);
    //付费类型: 0101-免费、0102-vip免费、0103-付费
    String payType = albumInfo.getPayType();
    //TODO:处理付费标识 找出哪些需要付费或者进一步验证购买情况
    //2.处理未登录情况,获取到专辑信息得到专辑价格类型 VIP免费/付费 将除了免费试听以外的声音全部设置付费标识
    if (userId == null) {
        if (SystemConstant.ALBUM_PAY_TYPE_VIPFREE.equals(payType) || SystemConstant.ALBUM_PAY_TYPE_REQUIRE.equals(payType)) {
            //采用Stream流过滤,将声音列表中序号大于专辑中设置免费试听集数(数值)全部将付费标识改为true
            trackList.stream()
                    .filter(trackInfo -> trackInfo.getOrderNum() > albumInfo.getTracksForFree()) //过滤找出需要付费声音
                    .collect(Collectors.toList()) //收集为集合
                    .stream().forEach(trackInfo -> {  //再次对过滤后集合中元素遍历
                        //修改付费标识
                        trackInfo.setIsShowPaidMark(true);
                    });
        }
    } else {
        //3.处理已登录情况
        boolean isNeedCheckPaySatue = false;
        UserInfoVo userInfoVo = userFeignClient.getUserInfoVoById(userId).getData();
        //3.1 远程调用用户服务获取用户信息,得到用户VIP状态是否过期
        Integer isVip = userInfoVo.getIsVip();
        //3.2 判断专辑付费类型如果是VIP免费或者付费 且登录用户为普通用户或者VIP会员过期 进一步判断用户购买情况
        if (SystemConstant.ALBUM_PAY_TYPE_VIPFREE.equals(payType) || SystemConstant.ALBUM_PAY_TYPE_REQUIRE.equals(payType)) {
            //3.2.1 如果是普通用户
            if (isVip.intValue() == 0) {
                isNeedCheckPaySatue = true;
            }
            //3.2.2 如果是VIP会员但是会员过期
            if (isVip.intValue() == 1 && new Date().after(userInfoVo.getVipExpireTime())) {
                isNeedCheckPaySatue = true;
            }
        }

        //3.3 判断专辑付费类型如果是付费 无论普通用户或者VIP会员 进一步判断用户购买情况
        if (SystemConstant.ALBUM_PAY_TYPE_REQUIRE.equals(payType)) {
            isNeedCheckPaySatue = true;
        }
        //3.4 统一集中处理需要进一步判断用户购买情况,调用用户微服务得到本页中声音购买情况
        if (isNeedCheckPaySatue) {
            //3.5 将本页中需要校验声音ID获取到(去掉免费试听声音ID)调用用户服务得到本页中声音购买情况
            List<AlbumTrackListVo> needCheckTrackList =
                    trackList.stream()
                            .filter(trackInfo -> trackInfo.getOrderNum() > albumInfo.getTracksForFree())
                            .collect(Collectors.toList());

            List<Long> needCheckTrackIdList =
                    needCheckTrackList.stream()
                            .map(AlbumTrackListVo::getTrackId)
                            .collect(Collectors.toList());
            Map<Long, Integer> buyStatusMap = userFeignClient.userIsPaidTrack(userId, albumId, needCheckTrackIdList).getData();
            //3.6 根据用户服务响应购买情况,如果用户未购买,为声音设置付费标识:true
            for (AlbumTrackListVo albumTrackListVo : needCheckTrackList) {
                if (buyStatusMap.get(albumTrackListVo.getTrackId()) == 0) {
                    //用户未购买声音,显示付费标识
                    albumTrackListVo.setIsShowPaidMark(true);
                }
            }
        }
    }
    return pageInfo;
}

TrackInfoMapper接口:条件必须是当前已经开放并且是审核通过状态的数据,并且还需要获取到声音的播放量以及评论数量

/**
 * 查询指定专辑下包含声音列表
 *
 * @param pageInfo
 * @param albumId
 * @return
 */
Page<AlbumTrackListVo> getAlbumTrackPage(Page<AlbumTrackListVo> pageInfo, @Param("albumId") Long albumId);

TrackInfoMapper.xml 映射文件

动态SQL

#分页查询指定专辑下包含声音列表(包含统计信息)
select * from track_info where album_id = 307;
select * from track_info where album_id = 307 and id = 16289;
select * from track_stat where track_id = 16289;


select
    ti.id trackId,
    ti.track_title trackTitle,
    ti.media_duration mediaDuration,
    ti.order_num orderNum,
    ti.create_time createTime,
    max(if(ts.stat_type='0701', ts.stat_num, 0)) playStatNum,
    max(if(ts.stat_type='0702', ts.stat_num, 0)) collectStatNum,
    max(if(ts.stat_type='0703', ts.stat_num, 0)) praiseStatNum,
    max(if(ts.stat_type='0704', ts.stat_num, 0)) commentStatNum
    from track_info ti left join track_stat ts
on ts.track_id = ti.id
where ti.album_id = 307 and ti.is_deleted = 0
group by ti.id
order by ti.order_num asc
<!--查询指定专辑下包含声音列表-->
<select id="getAlbumTrackPage" resultType="com.atguigu.tingshu.vo.album.AlbumTrackListVo">
    select
        ti.id trackId,
        ti.track_title trackTitle,
        ti.media_duration mediaDuration,
        ti.order_num orderNum,
        ti.create_time createTime,
        max(if(ts.stat_type='0701', ts.stat_num, 0)) playStatNum,
        max(if(ts.stat_type='0702', ts.stat_num, 0)) collectStatNum,
        max(if(ts.stat_type='0703', ts.stat_num, 0)) praiseStatNum,
        max(if(ts.stat_type='0704', ts.stat_num, 0)) commentStatNum
    from track_info ti left join track_stat ts
                                 on ts.track_id = ti.id
    where ti.album_id = #{albumId} and ti.is_deleted = 0
    group by ti.id
    order by ti.order_num asc
</select>

测试:

  • 手动增加用户购买专辑记录:user_paid_album
  • 手动增加用户购买声音记录:user_paid_track
  • 手动修改VIP会员:user_info

情况一:未登录情况,专辑付费类型:VIP免费 付费 查看声音列表->试听声音免费+其余都需要展示付费标识

情况二:登录情况

  • 普通用户
    • 免费 全部免费
    • VIP付费 试听声音免费+用户购买过专辑/声音,未购买展示付费标识
    • 付费:试听声音免费+用户购买过专辑/声音,未购买展示付费标识
  • VIP用户
    • 免费 全部免费
    • VIP付费 全部免费
    • 付费:试听声音免费+用户购买过专辑/声音,未购买展示付费标识

2、MongoDB文档型数据库

详情见:第5章 MongoDB入门.md

播放进度对应的实体类:

@Data
@Schema(description = "UserListenProcess")
@Document
public class UserListenProcess {

   @Schema(description = "id")
   @Id
   private String id;

   @Schema(description = "用户id")
   private Long userId;

   @Schema(description = "专辑id")
   private Long albumId;

   @Schema(description = "声音id,声音id为0时,浏览的是专辑")
   private Long trackId;

   @Schema(description = "相对于音频开始位置的播放跳出位置,单位为秒。比如当前音频总时长60s,本次播放到音频第25s处就退出或者切到下一首,那么break_second就是25")
   private BigDecimal breakSecond;

   @Schema(description = "是否显示")
   private Integer isShow;

   @Schema(description = "创建时间")
   private Date createTime;

   @Schema(description = "更新时间")
   private Date updateTime;

}

3、声音详情

3.1 获取声音播放进度

在播放声音的时候,会有触发一个获取播放进度的控制器!因为页面每隔10s会自动触发一次保存功能,会将数据写入MongoDB中。所以我们直接从MongoDB中获取到上一次声音的播放时间即可!

YAPI接口:http://192.168.200.6:3000/project/11/interface/api/71

service-user 微服务的 UserListenProcessApiController 控制器中添加

/**
 * 获取当前用户声音播放进度
 *
 * @param trackId 声音ID
 * @return
 */
@GuiGuLogin(required = false)  //true:必须登录  false:不登录
@GetMapping("/userListenProcess/getTrackBreakSecond/{trackId}")
public Result<BigDecimal> getTrackBreakSecond(@PathVariable Long trackId) {
    Long userId = AuthContextHolder.getUserId();
    BigDecimal breakSecond = userListenProcessService.getTrackBreakSecond(userId, trackId);
    return Result.ok(breakSecond);
}

UserListenProcessService接口

/**
 * 获取当前用户声音播放进度
 *
 * @param userId 用户ID
 * @param trackId 声音ID
 * @return
 */
BigDecimal getTrackBreakSecond(Long userId, Long trackId);

UserListenProcessServiceImpl实现类:

package com.atguigu.tingshu.user.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.common.constant.KafkaConstant;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.service.KafkaService;
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.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.concurrent.TimeUnit;

@Service
@SuppressWarnings({"all"})
public class UserListenProcessServiceImpl implements UserListenProcessService {

    @Autowired
    private MongoTemplate mongoTemplate;

    /**
     * 获取当前用户声音播放进度
     *
     * @param userId  用户ID
     * @param trackId 声音ID
     * @return
     */
    @Override
    public BigDecimal getTrackBreakSecond(Long userId, Long trackId) {
        //1.构建查询对象 查询条件:用户ID、声音ID 等值
        Query query = new Query();
        query.addCriteria(Criteria.where("userId").is(userId).and("trackId").is(trackId));
        //2.执行查询 手动指定用户播放进度集合名称  userListenProcess_用户ID

        //mongoTemplate.findOne(query, UserListenProcess.class, MongoUtil.MongoCollectionEnum.USER_LISTEN_PROCESS.getCollectionPrefix()+"_"+userId);
        UserListenProcess userListenProcess = mongoTemplate.findOne(query, UserListenProcess.class, MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_LISTEN_PROCESS, userId));
        if (userListenProcess != null) {
            return userListenProcess.getBreakSecond();
        }
        return new BigDecimal("0.00");
    }
}

3.2 更新播放进度

页面每隔10秒左右更新播放进度.

  1. 更新播放进度页面会传递 专辑Id ,秒数,声音Id 。后台会将这个三个属性封装到UserListenProcessVo 对象中。然后利用MongoDB进行存储到UserListenProcess实体类中!
  2. 为了提高用户快速访问,将用户信息存储到缓存中。先判断当前用户Id 与 声音Id 是否存在,不存在的话才将数据存储到缓存,并且要发送消息给kafka。
  3. kafka 监听消息并消费,更新专辑与声音的统计数据。

3.2.1 更新MongoDB

YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/73

UserListenProcessApiController 控制器中添加

/**
 * 更新声音播放进度及播放统计信息
 * @param userListenProcessVo
 * @return
 */
@GuiGuLogin(required = false)
@PostMapping("/userListenProcess/updateListenProcess")
public Result updateListenProcess(@RequestBody UserListenProcessVo userListenProcessVo){
    Long userId = AuthContextHolder.getUserId();
    userListenProcessService.updateListenProcess(userId, userListenProcessVo);
    return Result.ok();
}

UserListenProcessService接口:

/**
 * 更新声音播放进度及播放统计信息
 * @param userListenProcessVo
 * @return
 */
void updateListenProcess(Long userId, UserListenProcessVo userListenProcessVo);

UserListenProcessServiceImpl实现类:


@Autowired
private RedisTemplate redisTemplate;

@Autowired
private KafkaService kafkaService;


/**
 * 更新声音播放进度及播放统计信息
 *
 * @param userId              用户ID
 * @param userListenProcessVo 声音信息
 * @return
 */
@Override
public void updateListenProcess(Long userId, UserListenProcessVo userListenProcessVo) {
    //1.根据用户ID+声音ID查询播放进度
    Query query = new Query(Criteria.where("userId").is(userId).and("trackId").is(userListenProcessVo.getTrackId()));
    UserListenProcess userListenProcess = mongoTemplate.findOne(query, UserListenProcess.class, MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_LISTEN_PROCESS, userId));

    //1.1 如果存在则修改进度、更新时间
    BigDecimal breakSecond = userListenProcessVo.getBreakSecond();
    breakSecond.setScale(2, RoundingMode.HALF_UP);
    if (userListenProcess != null) {
        userListenProcess.setBreakSecond(breakSecond);
        userListenProcess.setUpdateTime(new Date());
    } else {
        //1.2 如果不存在则新增播放进度
        userListenProcess = new UserListenProcess();
        userListenProcess.setUserId(userId);
        userListenProcess.setAlbumId(userListenProcessVo.getAlbumId());
        userListenProcess.setTrackId(userListenProcessVo.getTrackId());
        userListenProcess.setBreakSecond(breakSecond);
        userListenProcess.setIsShow(1);
        userListenProcess.setCreateTime(new Date());
        userListenProcess.setUpdateTime(new Date());
    }
    mongoTemplate.save(userListenProcess, MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_LISTEN_PROCESS, userId));

    //2.异步基于MQ更新声音统计信息

    //2.1 确保24小时内/当前0点前播放进度只统计一次 基于Redis实现
    //2.1.1 构建避免用户多次对声音进行统计Key 形式:前缀+用户ID:声音ID
    String key = RedisConstant.USER_TRACK_REPEAT_STAT_PREFIX + userId + ":" + userListenProcessVo.getTrackId();
    //2.1.2 调用setnx命令对应api
    Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, userListenProcessVo.getTrackId().toString(), 24, TimeUnit.HOURS);
    //2.1.3 当存入Key成功-说明24小时内第一次统计
    if (flag) {
        //2.2 发送MQ消息到Kafka话题
        //2.2.1 创建用于更新声音统计信息VO对象
        TrackStatMqVo mqVo = new TrackStatMqVo();
        //产生消息唯一标识,用于消费者端幂等性判断处理
        mqVo.setBusinessNo(IdUtil.fastSimpleUUID());
        mqVo.setAlbumId(userListenProcessVo.getAlbumId());
        mqVo.setTrackId(userListenProcessVo.getTrackId());
        mqVo.setStatType(SystemConstant.TRACK_STAT_PLAY);
        mqVo.setCount(1);
        //2.2.2 发送消息
        kafkaService.sendMessage(KafkaConstant.QUEUE_TRACK_STAT_UPDATE, JSON.toJSONString(mqVo));
    }
}

3.2.2 更新MySQL统计信息

service-album 微服务中添加监听消息:

package com.atguigu.tingshu.album.receiver;

import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.album.service.TrackInfoService;
import com.atguigu.tingshu.common.constant.KafkaConstant;
import com.atguigu.tingshu.vo.album.TrackStatMqVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * @author: atguigu
 * @create: 2023-11-24 15:59
 */
@Slf4j
@Component
public class AlbumReceiver {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private TrackInfoService trackInfoService;

    /**
     * 监听声音统计信息(播放量、收藏量、点赞量、评论量)
     * 分析:需要进行幂等性处理,避免同一个消息被Kaka多次重复投递 需要进行事务管理
     *
     * @param record
     */
    @KafkaListener(topics = KafkaConstant.QUEUE_TRACK_STAT_UPDATE)
    public void updateTrackStat(ConsumerRecord<String, String> record) {
        String value = record.value();
        if (StringUtils.isNotBlank(value)) {
            log.info("[专辑服务]监听更新声音统计信息:{}", value);
            //1. 进行幂等性处理 同一个声音统计消息即使多次重复投递,只处理一次
            TrackStatMqVo mqVo = JSON.parseObject(value, TrackStatMqVo.class);
            String key = "biz:" + mqVo.getBusinessNo();
            Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, mqVo.getTrackId(), 1, TimeUnit.HOURS);
            //2. 调用业务层更新声音及专辑统计信息
            if (flag) {
                //说明第一次处理业务
                trackInfoService.updateTrackStat(mqVo);
            }
        }
    }
}

TrackInfoService 中添加接口

/**
 * 更新声音及专辑统计信息
 * @param mqVo
 */
void updateTrackStat(TrackStatMqVo mqVo);

TrackInfoServiceImpl 中添加实现

@Autowired
private AlbumInfoMapper albumInfoMapper;


/**
 * 更新声音及专辑统计信息
 *
 * @param mqVo
 */
@Override
@Transactional(rollbackFor = Exception.class)
public void updateTrackStat(TrackStatMqVo mqVo) {
    //1.更新声音统计信息(0701-播放量 0702-收藏量 0703-点赞量 0704-评论数)
    trackInfoMapper.updateStat(mqVo.getTrackId(), mqVo.getStatType(), mqVo.getCount());


    //2.更新专辑统计信息(0401-播放量 0402-订阅量 0403-购买量 0404-评论数')
    if (SystemConstant.TRACK_STAT_PLAY.equals(mqVo.getStatType())) {
        albumInfoMapper.updateStat(mqVo.getAlbumId(), SystemConstant.ALBUM_STAT_PLAY, mqVo.getCount());
    }
    if (SystemConstant.TRACK_STAT_COMMENT.equals(mqVo.getStatType())) {
        albumInfoMapper.updateStat(mqVo.getAlbumId(), SystemConstant.ALBUM_STAT_COMMENT, mqVo.getCount());
    }
}

TrackStatMapper.java 添加方法

/**
 * 修改声音统计信息
 *
 * @param trackId
 * @param statType
 * @param count
 */
//@Select()
//@Update()
//@Insert()
@Update("UPDATE track_stat set stat_num = stat_num + #{count} where track_id = #{trackId} and stat_type = #{statType}")
void updateStat(@Param("trackId") Long trackId, @Param("statType") String statType, @Param("count") Integer count);

AlbumStatMapper.java 接口添加

package com.atguigu.tingshu.album.mapper;

import com.atguigu.tingshu.model.album.AlbumStat;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;

@Mapper
public interface AlbumStatMapper extends BaseMapper<AlbumStat> {


   void updateStat(@Param("albumId") Long albumId, @Param("statType") String statType, @Param("count") Integer count);
}

AlbumInfoMapper.xml

<update id="updateStat">
    UPDATE album_stat set stat_num = stat_num + #{count} where album_id = #{albumId} and stat_type = #{statType}
</update>

3.3 获取声音统计信息

YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/75

统计声音需要更新的数据如下,我们将数据封装到一个实体类中便于操作

@Data
@Schema(description = "用户声音统计信息")
public class TrackStatVo {


   @Schema(description = "播放量")
   private Integer playStatNum;

   @Schema(description = "订阅量")
   private Integer collectStatNum;

   @Schema(description = "点赞量")
   private Integer praiseStatNum;

   @Schema(description = "评论数")
   private Integer commentStatNum;  //该属性需要修改

}

TrackInfoApiController 控制器中添加

/**
 * 根据声音ID,获取声音统计信息
 * @param trackId
 * @return
 */
@Operation(summary = "根据声音ID,获取声音统计信息")
@GetMapping("/trackInfo/getTrackStatVo/{trackId}")
public Result<TrackStatVo> getTrackStatVo(@PathVariable Long trackId){
    return Result.ok(trackInfoService.getTrackStatVo(trackId));
}

TrackInfoService接口

/**
 * 根据声音ID,查询声音统计信息
 * @param trackId
 * @return
 */
TrackStatVo getTrackStatVo(Long trackId);

TrackInfoServiceImpl实现类

/**
 * 根据声音ID,查询声音统计信息
 * @param trackId
 * @return
 */
@Override
public TrackStatVo getTrackStatVo(Long trackId) {
    return trackInfoMapper.getTrackStatVo(trackId);
}

TrackInfoMapper.java

/**
 * 获取声音统计信息
 * @param trackId
 * @return
 */
@Select("select\n" +
        "    track_id,\n" +
        "    max(if(stat_type='0701', stat_num, 0)) playStatNum,\n" +
        "    max(if(stat_type='0702', stat_num, 0)) collectStatNum,\n" +
        "    max(if(stat_type='0703', stat_num, 0)) praiseStatNum,\n" +
        "    max(if(stat_type='0704', stat_num, 0)) commentStatNum\n" +
        "    from track_stat where track_id = #{trackId} and is_deleted=0\n" +
        "group by track_id")
TrackStatVo getTrackStatVo(@Param("trackId") Long trackId);

SQL

# 根据声音ID查询指定声音统计信息 playStatNum collectStatNum praiseStatNum commentStatNum
select
    track_id,
    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_stat where track_id = 49162 and is_deleted=0
group by track_id

3.4 专辑上次播放专辑声音

image-20231012111356796

我们需要根据用户Id 来获取播放记录 ,需要获取到专辑Id 与声音Id 封装到map中然后返回数据即可!

YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/83

控制器 UserListenProcessApiController

/**
 * 获取当前用户上次播放专辑声音记录
 *
 * @return
 */
@GuiGuLogin
@GetMapping("/userListenProcess/getLatelyTrack")
public Result<Map<String, Long>> getLatelyTrack() {
    Long userId = AuthContextHolder.getUserId();
    return Result.ok(userListenProcessService.getLatelyTrack(userId));
}

UserListenProcessService接口:

/**
 * 获取用户最近一次播放记录
 * @param userId
 * @return
 */
Map<String, Long> getLatelyTrack(Long userId);

UserListenProcessServiceImpl实现类

/**
 * 获取用户最近一次播放记录
 *
 * @param userId
 * @return
 */
@Override
public Map<String, Long> getLatelyTrack(Long userId) {
    //根据用户ID查询播放进度集合,按照更新时间倒序,获取第一条记录
    //1.构建查询条件对象
    Query query = new Query();
    //1.1 封装用户ID查询条件
    query.addCriteria(Criteria.where("userId").is(userId));
    //1.2 按照更新时间排序
    query.with(Sort.by(Sort.Direction.DESC, "updateTime"));
    //1.3 只获取第一条记录
    query.limit(1);
    //2.执行查询
    UserListenProcess listenProcess = mongoTemplate.findOne(query, UserListenProcess.class, MongoUtil.getCollectionName(MongoUtil.MongoCollectionEnum.USER_LISTEN_PROCESS, userId));
    if (listenProcess != null) {
        //封装响应结果
        Map<String, Long> mapResult = new HashMap<>();
        mapResult.put("albumId", listenProcess.getAlbumId());
        mapResult.put("trackId", listenProcess.getTrackId());
        return mapResult;
    }
    return null;
}

4、更新Redis排行榜

手动调用一次更新,查看排行榜。后续会整合xxl-job 分布式定时任务调度框架做定时调用。

image-20231012114427463

YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/77

service-album微服务中BaseCategoryApiController控制器中添加

/**
 * 查询所有一级分类列表
 * @return
 */
@Operation(summary = "查询所有一级分类列表")
@GetMapping("/category/findAllCategory1")
public Result<List<BaseCategory1>> getAllCategory1() {
    return Result.ok(baseCategoryService.list());
}

AlbumFeignClient

/**
 * 查询所有一级分类列表
 * @return
 */
@GetMapping("/category/findAllCategory1")
public Result<List<BaseCategory1>> getAllCategory1();

AlbumDegradeFeignClient熔断类

@Override
public Result<List<BaseCategory1>> getAllCategory1() {
    log.error("[专辑模块Feign调用]getAllCategory1异常");
    return null;
}

YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/79

SearchApiController 中添加控制器

/**
 * 仅用于测试-手动将不同分类下ES热门专辑存入Redis中
 * @return
 */
@Operation(summary = "仅用于测试-手动将不同分类下ES热门专辑存入Redis中")
@GetMapping("/albumInfo/updateLatelyAlbumRanking")
public Result updateLatelyAlbumRanking(){
    searchService.updateLatelyAlbumRanking();
    return Result.ok();
}

SearchService接口:

/**
 * 查询ES中不同分类下热门专辑列表,将不同分类下不同排序热门专辑放入Redis
 */
void updateLatelyAlbumRanking();

SearchServiceImpl实现类:


@Autowired
private RedisTemplate redisTemplate;

/**
 * 查询ES中不同分类下热门专辑列表,将不同分类下不同排序热门专辑放入Redis
 */
@Override
public void updateLatelyAlbumRanking() {
    try {
        //1.远程调用专辑服务获取所有一级分类列表
        List<BaseCategory1> category1List = albumFeignClient.getAllCategory().getData();
        Assert.notNull(category1List, "一级分类集合为空");

        //2.遍历一级分类列表,每遍历一次查询当前一级分类下,不同排序方式 热门专辑列表
        for (BaseCategory1 baseCategory1 : category1List) {
            Long category1Id = baseCategory1.getId();
            //2.1 调用ES进行检索索引库
            String[] rankingDimensionArray = new String[]{"hotScore", "playStatNum", "subscribeStatNum", "buyStatNum", "commentStatNum"};
            for (String rankingDimension : rankingDimensionArray) {
                //2.2 遍历排序字段,没处理一个排序字段 得到某个分类下某个排序字段对应热门专辑
                SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(
                        s -> s.index(INDEX_NAME)
                                .query(q -> q.term(t -> t.field("category1Id").value(category1Id)))
                                .size(20)
                                .sort(sf -> sf.field(f -> f.field(rankingDimension).order(SortOrder.Desc)))

                        , AlbumInfoIndex.class
                );
                //2.3 解析获取热门专辑列表
                List<Hit<AlbumInfoIndex>> hits = searchResponse.hits().hits();
                if (CollectionUtil.isNotEmpty(hits)) {
                    //3.将热门专辑存入Redis
                    List<AlbumInfoIndex> infoIndexList = hits.stream().map(hit -> hit.source()).collect(Collectors.toList());
                    //2.4 将当前分类下指定排序字段热门专辑放入Redis Hash结构中
                    //2.4.1 构建Redis排行数据Hash结构Key
                    String rangingKey = RedisConstant.RANKING_KEY_PREFIX + category1Id;
                    //2.4.2 构建REdis中Hash中field
                    String fieldKey = rankingDimension;
                    //2.4.3 存入Hash中
                    redisTemplate.opsForHash().put(rangingKey, fieldKey, infoIndexList);
                }
            }
        }
    } catch (Exception e) {
        log.error("[专辑服务]导入热门专辑ES异常:{}", e);
        throw new RuntimeException(e);
    }
}

5、获取排行榜

image-20231012114420751

点击排行榜的时候,能看到获取排行榜的地址

排行榜:key=ranking:category1Id field = hotScore 或 playStatNum 或 subscribeStatNum 或 buyStatNum 或albumCommentStatNum value=List

YAPI接口地址:http://192.168.200.6:3000/project/11/interface/api/81

SearchApiController 控制器中添加

/**
 * 根据分类ID及热度方式查询专辑列表
 * @param category1Id
 * @param dimension
 * @return
 */
@Operation(summary = "根据分类ID及热度方式查询专辑列表")
@GetMapping("/albumInfo/findRankingList/{category1Id}/{dimension}")
public Result<List<AlbumInfoIndex>> getRankingList(@PathVariable Long category1Id, @PathVariable String dimension){
    List<AlbumInfoIndex> list = searchService.getRankingList(category1Id, dimension);
    return Result.ok(list);
}

SearchService接口:

/**
 * 根据分类ID及热度方式查询专辑列表
 * @param category1Id
 * @param dimension
 * @return
 */
List<AlbumInfoIndex> getRankingList(Long category1Id, String dimension);

SearchServiceImpl实现类

/**
 * 根据分类ID及热度方式查询专辑列表
 *
 * @param category1Id
 * @param dimension
 * @return
 */
@Override
public List<AlbumInfoIndex> getRankingList(Long category1Id, String dimension) {
    //1.构建热度hash结构的Key
    String rankingKey = RedisConstant.RANKING_KEY_PREFIX + category1Id;
    //2.构建排序字段hashKey
    List<AlbumInfoIndex> list = (List<AlbumInfoIndex>) redisTemplate.opsForHash().get(rankingKey, dimension);
    return list;
}