基于Redis实现关注、取关、共同关注及消息推送(含源码)

微信公众号访问地址基于Redis实现关注、取关、共同关注及消息推送(含源码)

推荐文章:

    1、springBoot对接kafka,批量、并发、异步获取消息,并动态、批量插入库表;

    2、SpringBoot用线程池ThreadPoolTaskExecutor异步处理百万级数据;

    3、为什么引入Redisson分布式锁?

    4、Redisson可重入锁原理

    5、SpringBoot整合多数据源,并支持动态新增与切换(详细教程)

一、简介

       实现用户之间的关注和取消关注、查询是否关注、共同关注及关注后消息采用feed方式推送及滚动分页查看效果等相关功能。利用redis里面的Set集合实现关注,取关,共同关注,消息推送等,结合Java代码实现具体的功能。

二、实现关注和取关

2.1、关注和取消关注功能

业务流程图:

核心代码实现:

public Result follow(Long followUserId, Boolean isFollow,Long userId) {
        // 1.获取登录用户 暂时先缓存前端传用户ID
//        Long userId = UserHolder.getUser().getId();
        String key = "follows:" + userId;
        // 1.判断到底是关注还是取关
        if (isFollow) {
            // 2.关注,新增数据
            Follow follow = new Follow();
            follow.setUserId(userId);  //当前登录人员ID
            follow.setFollowUserId(followUserId);  //关注用户的id
            boolean isSuccess = save(follow);
            if (isSuccess) {
                // 把关注用户的id,放入redis的set集合 sadd userId followerUserId
                stringRedisTemplate.opsForSet().add(key, followUserId.toString());
            }
        } else {
            // 3.取关,删除 delete from tb_follow where user_id = ? and follow_user_id = ?
            boolean isSuccess = remove(new QueryWrapper<Follow>()
                    .eq("user_id", userId).eq("follow_user_id", followUserId));
            if (isSuccess) {
                // 把关注用户的id从Redis集合中移除
                stringRedisTemplate.opsForSet().remove(key, followUserId.toString());
            }
        }
        return Result.ok();
    }
 

结果展示:

2.2、共同关注功能

2.3、使用的redis命令如下:

SINTER key1 [key2]返回给定所有集合的交集

2.4、代码实现

public Result followCommons(Long id,Long userId) {
        // 1.获取当前用户
//        Long userId = UserHolder.getUser().getId();
        String key = "follows:" + userId;
        // 2.求交集
        String key2 = "follows:" + id;
        Set<String> intersect = stringRedisTemplate.opsForSet().intersect(key, key2);
        if (intersect == null || intersect.isEmpty()) {
            // 无交集
            return Result.ok(Collections.emptyList());
        }
        // 3.解析id集合
        List<Long> ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList());
        // 4.查询用户
        List<UserDTO> users = userService.listByIds(ids)
                .stream()
                .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
                .collect(Collectors.toList());
        return Result.ok(users);
    }

2.5、结果展示

三、关注推送

3.1、Feed流模式

3.2、常见模式

Feed流产品有两种常见模式:

●Timeline:不做内容筛选,简单的按照内容发布时间排序,常用于好友或关注。例如:朋友圈。

    >优点:信息全面,不会有缺失。并且实现也相对简单。

    >缺点:信息噪音较多,用户不一定感兴趣,内容获取效率低。

●智能排序:利用智能算法屏蔽掉违规的、用户不感兴趣的内容。推送用户感兴趣信息来吸引用户

    >优点:投喂用户感兴趣信息,用户粘度很高,容易沉迷。

    >缺点:如果算法不精准,可能起到反作用。

     本例中的个人页面,是基于关注的好友来做Feed流,因此采用Timeline的模式。该模式的实现方案有三种:

①拉模式   ②推模式    ③推拉结合

3.3、三种实现方案

3.3.1、拉模式

说明:粉丝主动去拉取相关信息。

3.3.2、推模式

说明:博主主动推送相关信息给粉丝。

3.3.3、推拉结合模式

说明活跃粉丝:博主主动推送相关信息给粉丝。普通粉丝:粉丝主动去拉取相关信息。

3.3.4、总结

       对于大部分中小型公司需求,采取推模式基本上满足需求,对于超千万的用户量的大型公司,需要采取推拉模式,但是实现上相比就复杂多了。

四、案例

4.1、需求分析

滚动分页:

4.2、使用到的redis命令

1、ZREVRANGEBYSCORE:Redis的一个Sorted Set命令,用于按照分数从高到低的顺序返回满足指定分数范围条件的元素。它的语法如下:

ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]

参数解释如下:

    key:排序集合的键名。

    max:分数范围的上限,可以使用"+inf"表示正无穷大。

    min:分数范围的下限,可以使用"-inf"表示负无穷大。

    WITHSCORES:可选参数,表示返回结果时同时返回元素与分数。

    LIMIT:可选参数,用于限制返回结果的偏移量和数量。

示例用法:

ZREVRANGEBYSCORE myset 100 0ZREVRANGEBYSCORE myset (100 0 WITHSCORESZREVRANGEBYSCORE myset 100 0 LIMIT 0 10

第一个示例命令将返回分数从最高到最低的所有元素,分数范围为(100,0],不包括100。

第二个示例命令将返回分数从最高到最低的所有元素及其对应的分数,分数范围为(100,0],不包括100。

第三个示例命令将返回分数从最高到最低的前10个元素,分数范围为(100,0],不包括100。

注意:ZREVRANGEBYSCORE是按照分数从高到低的顺序返回结果的,如果需要按照分数从低到高的顺序返回结果,可以使用ZRANGEBYSCORE命令。

2、ZADD:在Redis中,ZADD命令用于向有序集合(Sorted Set)中添加一个或多个成员。有序集合是一种将成员与分数(score)关联的数据结构,通过分数可以对成员进行排序。

ZADD命令的语法如下:

ZADD key [NX|XX] [CH] [INCR] score member [score member ...]

参数说明:

    key:有序集合的键名。

    NX:仅在键不存在时添加成员。

    XX:仅在键已经存在时添加成员。

    CH:返回修改的成员数量,包括新增和更新的成员。

    INCR:对已经存在的成员的分数进行自增操作。

    score:成员的分数,用于排序。

    member:要添加的成员。

示例:

ZADD myset 1 "member1"ZADD myset 2 "member2" 3 "member3"

注意:ZADD命令在Redis版本2.4以及以上版本可用。

reids中实现滚动分页功能:

4.3、业务流程图

4.4、核心代码展示

4.4.1、保存探店笔记并发送收件箱

  public Result saveBlog(Blog blog) {
        Long userId = 999L;  //暂时写死
        // 1.获取登录用户
//        UserDTO user = UserHolder.getUser();
//        blog.setUserId(user.getId());
        blog.setUserId(userId);
        // 2.保存探店笔记
        boolean isSuccess = save(blog);
        if(!isSuccess){
            return Result.fail("新增笔记失败!");
        }
        // 3.查询笔记作者的所有粉丝 select * from tb_follow where follow_user_id = ?
        List<Follow> follows = followService.query().eq("follow_user_id", userId).list();
        //非空判断
        if(CollUtil.isEmpty(follows)){
            return Result.ok();
        }
        // 4.推送笔记id给所有粉丝
        for (Follow follow : follows) {
            // 4.1.获取粉丝id
            Long uId = follow.getUserId();
            // 4.2.feed推送消息
            String key = FEED_KEY + uId;
            stringRedisTemplate.opsForZSet().add(key, blog.getId().toString(), System.currentTimeMillis());
        }
        // 5.返回id
        return Result.ok(blog.getId());
    }

4.4.2、查询关注的所有探店笔记(并含滚动分页)

public Result queryBlogOfFollow(Long max, Integer offset) {
        // 1.获取当前用户
//        Long userId = UserHolder.getUser().getId();
        Long userId = 111L;
        // 2.查询收件箱 ZREVRANGEBYSCORE key Max Min LIMIT offset count
        String key = FEED_KEY + userId;
        Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
                .reverseRangeByScoreWithScores(key, 0, max, offset, 2);
        // 3.非空判断
        if (typedTuples == null || typedTuples.isEmpty()) {
            return Result.ok();
        }
        // 4.解析数据:blogId、minTime(时间戳)、offset
        List<Long> ids = new ArrayList<>(typedTuples.size());
        long minTime = 0; // 2
        int os = 1; // 2
        for (ZSetOperations.TypedTuple<String> tuple : typedTuples) { // 5 4 4 2 2
            // 4.1.获取id
            ids.add(Long.valueOf(tuple.getValue()));
            // 4.2.获取分数(时间戳)
            long time = tuple.getScore().longValue();
            if(time == minTime){
                os++;
            }else{
                minTime = time;
                os = 1;
            }
        }
        // 5.根据id查询blog
        String idStr = StrUtil.join(",", ids);
        List<Blog> blogs = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();
        for (Blog blog : blogs) {
            // 5.1.查询blog有关的用户
            queryBlogUser(blog);
            // 5.2.查询blog是否被点赞
            isBlogLiked(blog);
        }
        // 6.封装并返回
        ScrollResult r = new ScrollResult();
        r.setList(blogs);
        r.setOffset(os);
        r.setMinTime(minTime);
        return Result.ok(r);
    }

备注:

4.4.3、请求参数

参考案例:

{  "comments": 0,  "content": "生活就是一半烟火·一半诗意",  "createTime": "2023-08-17T05:17:34.287Z",  "id": 90,  "images": "111111",  "liked": 0,  "name": "11111",  "shopId": 10,  "title": "生活就是一半烟火",  "updateTime": "2023-08-17T05:17:34.287Z",  "userId": 999}

五、源码获取方式

     更多优秀文章,请关注个人微信公众号或搜索“程序猿小杨”查阅。然后回复:源码,可以获取该项目对应的源码及表结构,开箱即可使用。

说明:后面redis相关操作的功能都会放在此文件夹中,需要相关功能的,只需要获取最新的资源,替换项目即可

       如果大家对相关文章感兴趣,可以关注微信公众号"程序猿小杨",会持续更新优秀文章!欢迎大家 分享、收藏、点赞、在看,您的支持就是我坚持下去的最大动力!谢谢!

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值