java实现短视频推荐打散算法
“上次写的思路还在上次的文章中”
以下只是自己进行打散的逻辑,可能有一堆坑😓,只是个实现思路。
代码实现
@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放入结果集,递归退出条件是没有临时记录的作者信息tmp
,tmp
为空就意味着result
已经满了。
参考
- redis工具类参考:SpringBoot 操作 Redis 详解
转载自:https://juejin.cn/post/7030047579102674951