当前位置: 首页 > news >正文

达人探店和好友关注功能(feed流的使用,滚动分页查询)

目录

  • 达人探店
    • 一:发布笔记
    • 二:点赞功能
    • 三:点赞排行
  • 好友关注
    • 一:关注和取关
    • 二:共同关注
    • 三:关注推送
      • 1:feed流实现方案分析
      • 2:基于推模式实现关注推送功能
      • 3:滚动分页查询

达人探店

一:发布笔记

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

controller:

@GetMapping("/{id}")
public  Result queryBlog(@PathVariable Long id){return blogService.queryBlog(id);
}

service:

@Override
public Result queryBlog(Long id) {Blog blog = getById(id);if (blog==null){return Result.fail("当前博客不存在!");}Long userId = blog.getUserId();User user = userService.getById(id);String nickName = user.getNickName();String icon = user.getIcon();blog.setIcon(icon);blog.setName(nickName);return Result.ok(blog);
}

二:点赞功能

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

@Override
@Transactional
public Result queryLike(Long id) {String key = RedisConstants.BLOG_LIKED_KEY + id;Long userId = UserHolder.getUser().getId();Long add = stringRedisTemplate.opsForSet().add(key, userId.toString());if (add == 0) {boolean ifSuccess = update().setSql("liked=liked-1").eq("id", id).update();if (BooleanUtil.isTrue(ifSuccess)) {stringRedisTemplate.opsForSet().remove(key, userId.toString());}} else {update().setSql("liked=liked+1").eq("id", id).update();}return Result.ok();
}

然后在根据id查询blog业务中,判断用户有没有点赞,对islike赋值

早blog的分页查询中,也是判断用户有没有点赞,然后对islike赋值;

供前端来响应体现是否高亮

三:点赞排行

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

点赞排行榜是在有多人点赞时,在点赞位置显示头像相关信息,而且是按照点赞顺序先后顺序进行排序,我们之前将点赞的用户全部存入了redis中的set集合中,想要取出按时间排序的是不行的,只有将他zset,score按照时间进行排序。

我们存入zset中时,以当前的时间戳作为score,这样越小的排在越前面,因为时间越小时间戳越小;

然后我们显示是否有没有点赞的时候需要判断当前用户有没有在zset中,因为zset中没有判断值是否存在的方法,只有返回score的方法,我们可以利用这个方法,score有值说明用户存在,为空用户不存在;

Long userId =user.getId();
Boolean add = stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());
if (BooleanUtil.isFalse(add)) {boolean ifSuccess = update().setSql("liked=liked-1").eq("id", id).update();if (BooleanUtil.isTrue(ifSuccess)) {stringRedisTemplate.opsForZSet().remove(key, userId.toString());}
} else {update().setSql("liked=liked+1").eq("id", id).update();
}
return Result.ok();

取出时:

Double member = stringRedisTemplate.opsForZSet().score(RedisConstants.BLOG_LIKED_KEY + id, UserHolder.getUser().getId().toString());
blog.setIsLike(member != null);

然后获取点赞用户的前五名,就要从集合中取:

@Override
public Result queryLikes(Long id) {String key=RedisConstants.BLOG_LIKED_KEY + id;//从set中获取排行榜前五的用户idSet<String> range = stringRedisTemplate.opsForZSet().range(key, 0, 4);if (range==null||range.isEmpty()){return Result.ok();}//使用流式编程将id取出且变成long类型的集合List<Long> collect = range.stream().map(Long::valueOf).collect(Collectors.toList());//将集合中的元素转成字符串并且用‘,’隔开;String join = StrUtil.join(",", collect);//调用mp的方法,根据id集合查询用户List<User> users = userService.query().in("id",collect).last("order by field(id,"+ join+")").list();//将用户转成用户dtoList<UserDTO> userDTOS = BeanUtil.copyToList(users, UserDTO.class);return Result.ok(userDTOS);
}

因为mysql的in()查询的顺序和我们传入的顺序是不一样的所以我们自己手写sql:

List<User> users = userService.query().in("id",collect).last("order by field(id,"+ join+")").list();

好友关注

一:关注和取关

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

关注:

@Override
public Result follow(Long id, Boolean isFollow) {Long userId = UserHolder.getUser().getId();Follow follow = new Follow();if (isFollow) {follow.setUserId(userId);follow.setFollowUserId(id);save(follow);}else {remove(new QueryWrapper<Follow>().eq("user_id",userId).eq("follow_user_id",id));}return Result.ok();
}

如果是关注就将数据存入follow中,如果是取关就将这条数据从follow中删除

判断是否关注:

@Override
public Result isFollow(Long id) {Long userId = UserHolder.getUser().getId();Long count = query().eq("user_id", userId).eq("follow_user_id", id).count();return Result.ok(count>0);
}

二:共同关注

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

共同关注的功能我们可以借助redis的set集合的求交集并集来求,所以在我们关注了别人或者,取关了别人时,要将数据从redis中取出或删除:

@Override
@Transactional
public Result follow(Long id, Boolean isFollow) {Long userId = UserHolder.getUser().getId();String key= "follows:"+userId;Follow follow = new Follow();if (isFollow) {follow.setUserId(userId);follow.setFollowUserId(id);save(follow);//存入redisstringRedisTemplate.opsForSet().add(key,id.toString());}else {remove(new QueryWrapper<Follow>().eq("user_id",userId).eq("follow_user_id",id));//从redis中删除:stringRedisTemplate.opsForSet().remove(key,id.toString());}return Result.ok();
}

然后就是求交集:

@Override
public Result commonFollow(Long id) {Long userId = UserHolder.getUser().getId();String keyPreFix="follows:";Set<String> intersect = stringRedisTemplate.opsForSet().intersect(keyPreFix + id, keyPreFix + userId);List<Long> collect = intersect.stream().map(Long::valueOf).collect(Collectors.toList());if (collect.isEmpty()||collect==null){return Result.ok();}List<User> users = userService.listByIds(collect);List<UserDTO> userDTOS = BeanUtil.copyToList(users, UserDTO.class);return Result.ok(userDTOS);}

三:关注推送

1:feed流实现方案分析

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

拉模式是将信息先存储到发件箱中,每次有人读的时候,再从发件箱中拉取消息到自己的收件箱,但是这种的话如果关注的人比较多,拉取的消息会变得异常的多,就会产生性能上的影响;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

推模式是直接将用户发的消息推送到关注用户的收件箱里;然后用户打开直接就能看到,但是如果是大v他的粉丝很多,并且有很多僵尸粉,如果全部都推过去就会浪费内存

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

推拉结合:对于大v,他的活跃粉丝我们采用推模式,因为经常看,读取的次数较多,如果是僵尸粉,我们采用拉模式,将消息放在发件箱里,只有读取的时候才会拉取;

对于普通用户,因为关注他的人并不多,所以我们可以采用推模式,直接将消息推送到粉丝的收件箱中,这样也不会浪费太多的内存;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2:基于推模式实现关注推送功能

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们要选择一个redis的数据结构来作为用户的收件箱,收件箱里保存博客的id,这个收件箱的要求就是可以根据时间进行排序,有两个结构满足条件,一个是list,早放进去的就在前面,还有就是zset,将时间戳当成score,然后根据score进行排序;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

然而我们的feed流是实时推送不断更新的,不能使用传统的分页查询,例如在读取第一页的时候,从最新的开始page=1,size=5,那么就会从索引为0开始读到4,但是这个时候又接受了新的消息,这个时候索引都变了,如果再读取第二页,page=2,size=5,开始从索引为5的读取,而因为接受了新的消息,原来索引为4的变成了索引为5的,就会出现重复读取,索引传统的分页不能使用

我们可以使用滚动分页,原理就是,page页码变成了最后一个读取的消息,那么list有办法记录最后一个读取的消息吗,没有因为list只有索引,而feed使索引也会变化;但是zset可以记录最后一个读取的score,所以我们选择zset;

完成推送的代码:

@Override
@Transactional
public Result saveBlog(Blog blog) {// 获取登录用户UserDTO user = UserHolder.getUser();blog.setUserId(user.getId());//查询所有该用户的所有粉丝List<Follow> list = followService.query().eq("follow_user_id", user.getId().toString()).list();for (Follow follow : list) {Long userId = follow.getUserId();stringRedisTemplate.opsForZSet().add(RedisConstants.FEED_KEY+userId,blog.getId().toString(),System.currentTimeMillis());}// 保存探店笔记save(blog);return Result.ok();
}

3:滚动分页查询

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

因为我们要根据score进行排序,所以要传入查询score的值的范围,score值的最大值,在第一次查询就是当前时间戳,在后面的查询的过程中就是上一次查询的score中的最小值,score的最小值就是0,这个不用管,然后就是offset,offset是,从获取到的score范围内第几个开始查,第一次查询都是0,后面的查询,都是从上一次查询的score的最小值的个数。开始查,然后就是查询的个数,这个也是固定的;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

定义一个类来封装返回的参数:

@Data
public class ScrollResult {//存储这一次查询的数据private List<?> list;//存储这一次查询的最小时间戳private Long minTime;//存储这一次查询最小时间戳的个数private Integer offset;
}

然后就是service的代码:

 @Override@Transactionalpublic Result queryBlogOFFollow(Long lastId, Integer offset) {//获取用户id用于查找用户的收件箱Long userId = UserHolder.getUser().getId();String key=RedisConstants.FEED_KEY+userId;//查询用户的收件箱,按指定方式查询:按照score倒序,socre最小值是0,最大值是传入的lastId(上一次查询的最小值),offset(偏移量),count(查询个数)Set<ZSetOperations.TypedTuple<String>> typedTuples =stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, 0, lastId, offset, 3);//判断是否为空if (typedTuples==null||typedTuples.isEmpty()){return Result.ok();}//先定义好变量用于接受数据返回给前端ArrayList<Long> ids = new ArrayList<>(typedTuples.size());long minTime=0;Integer os=1;//遍历收件箱,获取blog的id,和时间戳for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {String id = typedTuple.getValue();ids.add(Long.valueOf(id));long time = typedTuple.getScore().longValue();//时间戳就是一直更新,os是最小的时间戳出现的次数,如果不是最小的时间戳就是置为0if (minTime==time){os++;}else {minTime=time;os=1;}}//将blog的集合转成字符串并用‘,’隔开,然后因为mp的in直接传入集合会乱序,我们自己写order by field(顺序,也就是获取的id字符串)String join = StrUtil.join(",", ids);List<Blog> blogs = query().in("id", ids).last("order by field(id," + join + ")").list();//然后因为每个博客都有点赞和,用户信息,我们需要查询出来然后赋值for (Blog blog : blogs) {User user = userService.getById(userId);String nickName = user.getNickName();String icon = user.getIcon();blog.setIcon(icon);blog.setName(nickName);Double member = stringRedisTemplate.opsForZSet().score(RedisConstants.BLOG_LIKED_KEY + blog.getId(), UserHolder.getUser().getId().toString());blog.setIsLike(member != null);}//最后封装返回对象ScrollResult scrollResult = new ScrollResult();scrollResult.setList(blogs);scrollResult.setOffset(offset);scrollResult.setMinTime(minTime);return Result.ok(scrollResult);}
}
og.getId(), UserHolder.getUser().getId().toString());blog.setIsLike(member != null);}//最后封装返回对象ScrollResult scrollResult = new ScrollResult();scrollResult.setList(blogs);scrollResult.setOffset(offset);scrollResult.setMinTime(minTime);return Result.ok(scrollResult);}
}

http://www.mrgr.cn/news/64675.html

相关文章:

  • Django ORM详解:事务与F、Q函数使用
  • dockerdockerfiledocker-compose操作
  • 【django】django RESTFramework前后端分离框架快速入门
  • Freertos学习日志(1)-基础知识
  • TCP三次握手,四次挥手,以及11种状态详解
  • 春秋云境CVE-2022-21661,sqlmap+json一把梭哈
  • Python 有哪些优雅的代码实现让自己的代码更pythonic?
  • 串口接收,不定长数据接收
  • 00 递推和递归的核心讲解
  • LeetCode27:移除元素
  • JavaEE进阶---第一个SprintBoot项目创建过程我的感受
  • 1.kubernetes作用及组件
  • (五)PostgreSQL数据库操作示例
  • 如何使用Python WebDriver爬取ChatGPT内容(完整教程)
  • 我为何要用wordpress搭建一个自己的独立博客
  • Linux基础 文件与目录
  • int a[5]里面的 a表示a[0], a执行包含5个整数的数组的指针
  • OTFS的基本原理(通俗易懂)
  • 如何建购物网站提升用户体验
  • Goland2024 最新激活码
  • 大语言模型代码生成能力排行榜(2024年9月)
  • 海的记忆:海滨学院班级回忆录项目
  • 【综合算法学习】(第十五篇)
  • ComsolMatlab 基于准亥姆霍兹共振的可调谐水声超材料:从低频到超宽带
  • TOEIC 词汇专题:娱乐休闲篇
  • 【Python+Pycharm】2024-Python安装配置教程