likes
comments
collection
share

java实现短视频推荐打散算法

作者站长头像
站长
· 阅读数 58

“上次写的思路还在上次的文章中”

以下只是自己进行打散的逻辑,可能有一堆坑😓,只是个实现思路。

代码实现

@Slf4j
@Service
public class FirmVideoService {

    @Autowired
    private RedisList redisList;
    @Autowired
    private RedisHash redisHash;
    @Autowired
    private FixSortMapper fixSortMapper;

    
    /**
     * 查询推荐视频ID列表
     * @param videoId 游标:视频ID,默认从头(0)开始。视频游标可以每次获取视频时都能拿到还未看过的视
     频。可以解决分页查询视频时,会出现新增视频,旧的视频被挤到下一页,导致重复视频问题。
     * @param isFix 判断是否插入固定视频
     * @param size 每页大小
     */
    public List<VideoInfo> queryVideo(Integer videoId,int size,boolean isFix) {
        List<VideoInfo> videos = new ArrayList<>();
        LinkedHashSet dataSet = new LinkedHashSet<>();
        // 查询视频id对应作者id
        Map<Object, Object> objectMap = redisHash.hmGet("authors");

        // 将固定视频放入集合中
        List<Integer> fixList = new ArrayList<>();
        LinkedHashSet<Integer> authors = new LinkedHashSet<>();

        // videoId默认值或有固定标识,就优先显示固定视频
        if(0 == videoId || isFix){
            this.queryfixIds(fixList,objectMap,authors,size);
        }else{
            Integer[] fixIds = this.initVideo(size);
            fixList = Arrays.asList(fixIds);
        }

        // 查询计算后的视频id集合
        dataSet.addAll(this.queryFirmByRedis(videoId,size));

        // 构建结果集
        List<Integer> result = this.buildData(dataSet,fixList,authors,objectMap);
        result.forEach(s->{
            VideoInfo videoInfo = new VideoInfo();
            videoInfo.setVideoId(s.longValue());
            videos.add(videoInfo);
        });

        return videos;
    }

    /**
     * 查询固定视频id
     * @param fixList 视频ID列表
     * @param objectMap 视频作者对应关系
     * @param authors 作者记录set
     * @param size 每页大小
     */
    private void queryfixIds(List<Integer> fixList,Map<Object, Object> objectMap, LinkedHashSet<Integer> authors,int size){
        Integer[] fixIds = this.initVideo(size);
        this.addFixSorts(fixIds);
        fixList.addAll(Arrays.asList(fixIds));

        // 将固定视频放入结果集,并记录作者信息
        fixList.forEach(h->{
            AtomicInteger userIdAto = new AtomicInteger(0);
            Optional.ofNullable(objectMap.get(h.toString())).ifPresent(u->{
                userIdAto.set(Integer.parseInt(objectMap.get(h.toString()).toString()));
            });
            int authorId = userIdAto.get();
            // 记录作者信息
            authors.add(authorId);
        });
    }

    /**
     * 查询计算后的视频
     * @param videoId 上次看的视频ID
     * @param size 每页大小
     * @return
     */
    private List queryFirmByRedis(Integer videoId,int size){
        // 查询全部视频id
        Object msi = redisList.lRange("videos",0,-1);

        try {
            Optional.ofNullable(msi).orElseThrow(()->new Exception("firm video is null"));
        } catch (Exception e) {
            log.error("firm video is null");
            return new ArrayList();
        }
        ArrayList videoList = JSON.parseObject(msi.toString(), ArrayList.class);
        
        int index = 1;
        if(0 != videoId){
            // 获取上次查看视频的索引
            index += videoList.indexOf(videoId);
        }

        // 分页获取视频列表
        return videoList.subList(index,index+size);
    }


    /**
     * 固定热门视频添加
     * @param fixIds 返回视频ID列表
     */
    private void addFixSorts(Integer[] fixIds){
        // 配置强插,则其余视频按照优先级取余下视频,补足到 4 个
        List<FixSorts> fixSorts = fixSortMapper.select();

        // 强插数据不为空,顶替原视频
        if(!CollectionUtils.isEmpty(fixSorts)){
            fixSorts.forEach(h->{
                int index = h.getMediafixSort().intValue() - 1;
                int videoId = h.getTargetId().intValue();
                fixIds[index] = videoId;
            });
        }
    }

    /**
     * 递归构建视频列表
     * @param unused 未放到结果集中的视频ID
     * @param result 结果列表
     * @param tmp 临时表,保存作者ID
     * @param objectMap 视频对应作者映射
     * @return
     */
    private List<Integer> buildData(LinkedHashSet<Integer> unused,List<Integer> result,LinkedHashSet<Integer> tmp,Map<Object, Object> objectMap){

        // 只要有未放入结果集的视频就继续走插入逻辑
        Optional.ofNullable(unused).filter(u->u.size()>0).ifPresent(s->{
            s.forEach(videoId->{
                AtomicInteger userIdAto = new AtomicInteger(0);
                // 根据videoId查找作者ID
                Optional.ofNullable(objectMap.get(videoId.toString())).ifPresent(u->{
                    userIdAto.set(Integer.parseInt(objectMap.get(videoId.toString()).toString()));
                });
                int userId = userIdAto.get();
                // 判断是否这次作者ID没有出现过
                if(!tmp.contains(userId)){
                    // 往无数据的索引中插入视频id
                    for (int i = 0; i < result.size(); i++) {
                        if(0 == result.get(i)){
                            result.set(i,videoId);
                            tmp.add(userId);
                            break;
                        }
                    }
                }
            });
            if(tmp.isEmpty()){
                return;
            }
            // 清空作者信息
            tmp.clear();
            log.info("buildData result:{}",result);

            // 过滤,只保留还未放到结果集中的id
            LinkedHashSet<Integer> next = unused.stream().filter(e->!result.contains(e)).collect(Collectors.toCollection(LinkedHashSet::new));
            // 继续下一次放入结果
            buildData(next,result,tmp,objectMap);
        });
        return result;
    }

    /**
     * 初始化列表
     * @param size 每页大小
     * @return
     */
    private Integer[] initVideo(int size){
        Integer[] arr = new Integer[size];
        for (int i = 0; i < size; i++) {
            arr[i] = 0;
        }
        return arr;
    }

}

代码逻辑解释

queryVideo是获取推荐视频的入口方法,排序好的视频id列表(videos)和视频与作者id(authors)关系映射缓存在redis中。

首先判断是否显示固定位的视频,如果显示,就将fixList结果列表中对应槽放入固定位视频,并记录作者在authors集合中。

查询出全部准备推荐的视频,放到集合dataSet中。

准备好数据后进入buildData中递归将未放到结果集中的视频ID放入结果集,递归退出条件是没有临时记录的作者信息tmptmp为空就意味着result已经满了。

参考

转载自:https://juejin.cn/post/7030047579102674951
评论
请登录