最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

redis實現(xiàn)好友關注&消息推送的方法示例

 更新時間:2023年10月24日 10:16:58   作者:明礬java  
Redis作為一款開源的內存數(shù)據(jù)庫,具有可靠性、速度快、易用性等優(yōu)點,已經被廣泛應用于開發(fā)實際項目中,本文主要介紹了redis實現(xiàn)好友關注&消息推送的方法示例,感興趣的可以了解一下

關注和取消關注

在查看筆記詳情時,會自動發(fā)送請求,調用接口來檢查當前用戶是否已經關注了筆記作者,我們要實現(xiàn)這兩個接口

需求:基于該表數(shù)據(jù)結構,實現(xiàn)兩個接口:

  • 關注和取關接口
  • 判斷是否關注的接口

 關注是User之間的關系,是博主與粉絲的關系,數(shù)據(jù)庫中有一張tb_follow表來標識,我們要將用戶與博主關聯(lián)起來,只需要向這張表插入數(shù)據(jù)即可,需要將主鍵設為自增加,降低開發(fā)難度

 FollowController

//關注
@PutMapping("/{id}/{isFollow}")
public Result follow(@PathVariable("id") Long followUserId, @PathVariable("isFollow") Boolean isFollow) {
    return followService.follow(followUserId, isFollow);
}
//取消關注
@GetMapping("/or/not/{id}")
public Result isFollow(@PathVariable("id") Long followUserId) {
      return followService.isFollow(followUserId);
}

判斷當前用戶與博主是否已經關注了博主我們只需要查詢follow表中是否有記錄即可,等值查詢建議進行非空判斷,防止出現(xiàn)異常

@Override
    public Result isFollow(Long followUserId) {
        Long userId = UserHolder.getUser().getId();
        LambdaQueryWrapper<Follow> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(followUserId != null, Follow::getFollowUserId, followUserId)
                .eq(userId != null, Follow::getUserId, userId);
        Integer count = followMapper.selectCount(queryWrapper);
        return Result.ok(count > 0);
    }

前端發(fā)送請求時,會發(fā)送isFollow這個boolean值,用來讓后端判斷當前用戶是否已經關注了博主,如果為true,則直接插入數(shù)據(jù)庫,表示關注成功,如果為false,表示已經關注要取消關注了

@Override
    public Result follow(Long followUserId, Boolean isFollow) {
        Long userId = UserHolder.getUser().getId();
        String key = "follows:" + userId;
        if (isFollow) {
            //封裝follow對象
            Follow follow = new Follow();
            follow.setFollowUserId(followUserId);
            follow.setUserId(userId);
            follow.setCreateTime(LocalDateTime.now());
            //插入數(shù)據(jù)庫
            boolean isSuccess = save(follow);
        
        } else {
            LambdaQueryWrapper<Follow> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Follow::getFollowUserId, followUserId)
                    .eq(Follow::getUserId, userId);
            boolean isSuccess = remove(queryWrapper);
           
        }
        return Result.ok();
    }

共同關注

共同關注的好友,需要首先進入到這個頁面,這個頁面會發(fā)起兩個請求

1、去查詢用戶的詳情

2、去查詢用戶的筆記

 

// UserController 根據(jù)id查詢用戶
@GetMapping("/{id}")
public Result queryUserById(@PathVariable("id") Long userId){
	// 查詢詳情
	User user = userService.getById(userId);
	if (user == null) {
		return Result.ok();
	}
	UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
	// 返回
	return Result.ok(userDTO);
}
 
// BlogController  根據(jù)id查詢博主的探店筆記
@GetMapping("/of/user")
public Result queryBlogByUserId(
		@RequestParam(value = "current", defaultValue = "1") Integer current,
		@RequestParam("id") Long id) {
	// 根據(jù)用戶查詢
	Page<Blog> page = blogService.query()
			.eq("user_id", id).page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
	// 獲取當前頁數(shù)據(jù)
	List<Blog> records = page.getRecords();
	return Result.ok(records);
}

共同關注的需求:利用Redis中恰當?shù)臄?shù)據(jù)結構,實現(xiàn)共同關注功能。在博主個人頁面展示出當前用戶與博主的共同關注。在這里我們選用set集合,因為在set集合中,有交集并集補集的api,我們可以把兩人的關注的人分別放入到一個set集合中,然后再通過api去查看這兩個set集合中的交集數(shù)據(jù)。

首先對添加關注進行改造,在插入數(shù)據(jù)的同時也要將當前用戶關注的博主用戶id插入到set集合中去,用于查找交集

 @Override
    public Result follow(Long followUserId, Boolean isFollow) {
        Long userId = UserHolder.getUser().getId();
        String key = "follows:" + userId;
        if (isFollow) {
            //封裝follow對象
            Follow follow = new Follow();
            follow.setFollowUserId(followUserId);
            follow.setUserId(userId);
            follow.setCreateTime(LocalDateTime.now());
            //插入數(shù)據(jù)庫
            boolean isSuccess = save(follow);
            if (isSuccess) {
                stringRedisTemplate.opsForSet().add(key, followUserId.toString());
            }
        } else {
            LambdaQueryWrapper<Follow> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Follow::getFollowUserId, followUserId)
                    .eq(Follow::getUserId, userId);
            boolean isSuccess = remove(queryWrapper);
            if (isSuccess) {
                stringRedisTemplate.opsForSet().remove(key);
            }
        }
        return Result.ok();
    }

controller層,定義common接口,傳入的id是筆記博主的id

 @GetMapping("/common/{id}")
    public Result common(@PathVariable("id") Long id){
        System.out.println(id);
        return followService.commonFollow(id);
    }

service層

@Override
    public Result commonFollow(Long id) {
        //獲取當前用戶
        Long userId = UserHolder.getUser().getId();
        String key1 = "follows:" + userId;
        String key2 = "follows:" + id;
        Set<String> intersect = stringRedisTemplate.opsForSet().intersect(key1, key2);
        if(intersect == null || intersect.isEmpty()) {
            // 無交集
            return Result.ok(Collections.emptyList());
        }
        //解析用戶id集合
        List<Long> userIds = intersect.stream().map(item -> Long.valueOf(item)).collect(Collectors.toList());
        List<UserDTO> users = userService.selectByIds(userIds).stream().map(
                user -> BeanUtil.copyProperties(user, UserDTO.class)).collect(Collectors.toList());
        return Result.ok(users);
    }

Feed流實現(xiàn)方案

當我們關注了用戶后,這個用戶發(fā)了動態(tài),那么我們應該把這些數(shù)據(jù)推送給用戶,這個需求,其實我們又把他叫做Feed流,關注推送也叫做Feed流,直譯為投喂。為用戶持續(xù)的提供“沉浸式”的體驗,通過無限下拉刷新獲取新的信息。

對于傳統(tǒng)的模式的內容解鎖:我們是需要用戶去通過搜索引擎或者是其他的方式去解鎖想要看的內容

對于新型的Feed流的的效果:不需要我們用戶再去推送信息,而是系統(tǒng)分析用戶到底想要什么,然后直接把內容推送給用戶,從而使用戶能夠更加的節(jié)約時間,不用主動去尋找。類似B站,抖音等平臺的大數(shù)據(jù)推送機制

Feed流的兩種模式

Feed流的實現(xiàn)有兩種模式:

Feed流產品有兩種常見模式: Timeline:不做內容篩選,簡單的按照內容發(fā)布時間排序,常用于好友或關注。例如朋友圈

  • 優(yōu)點:信息全面,不會有缺失。并且實現(xiàn)也相對簡單
  • 缺點:信息噪音較多,用戶不一定感興趣,內容獲取效率低

智能排序:利用智能算法屏蔽掉違規(guī)的、用戶不感興趣的內容。推送用戶感興趣信息來吸引用戶

  • 優(yōu)點:投喂用戶感興趣信息,用戶粘度很高,容易沉迷
  • 缺點:如果算法不精準,可能起到反作用。 本例中的個人頁面,是基于關注的好友來做Feed流,因此采用Timeline的模式。該模式的實現(xiàn)方案有三種           

Timeline模式的實現(xiàn)方案

拉模式:也叫做讀擴散

該模式的核心含義就是:當張三和李四和王五發(fā)了消息后,都會保存在自己的郵箱中,假設趙六要讀取信息,那么他會從讀取他自己的收件箱,此時系統(tǒng)會從他關注的人群中,把他關注人的信息全部都進行拉取,然后在進行排序

優(yōu)點:比較節(jié)約空間,因為趙六在讀信息時,并沒有重復讀取,而且讀取完之后可以把他的收件箱進行清楚。

缺點:比較延遲,當用戶讀取數(shù)據(jù)時才去關注的人里邊去讀取數(shù)據(jù),假設用戶關注了大量的用戶,那么此時就會拉取海量的內容,對服務器壓力巨大。

推模式:也叫做寫擴散。

推模式是沒有寫郵箱的,當張三寫了一個內容,此時會主動的把張三寫的內容發(fā)送到他的粉絲收件箱中去,假設此時李四再來讀取,就不用再去臨時拉取了

優(yōu)點:時效快,不用臨時拉取  

缺點:內存壓力大,假設一個大V寫信息,很多人關注他, 就會寫很多分數(shù)據(jù)到粉絲那邊去

推拉結合模式:也叫做讀寫混合,兼具推和拉兩種模式的優(yōu)點。

推拉模式是一個折中的方案,站在發(fā)件人這一段,如果是個普通的人,那么我們采用寫擴散的方式,直接把數(shù)據(jù)寫入到他的粉絲中去,因為普通的人他的粉絲關注量比較小,所以這樣做沒有壓力,如果是大V,那么他是直接將數(shù)據(jù)先寫入到一份到發(fā)件箱里邊去,然后再直接寫一份到活躍粉絲收件箱里邊去,現(xiàn)在站在收件人這端來看,如果是活躍粉絲,那么大V和普通的人發(fā)的都會直接寫入到自己收件箱里邊來,而如果是普通的粉絲,由于他們上線不是很頻繁,所以等他們上線時,再從發(fā)件箱里邊去拉信息。即根據(jù)用戶的活躍程度來判斷是推模式還是拉模式

推送到粉絲收件箱

需求:

  • 修改新增探店筆記的業(yè)務,在保存blog到數(shù)據(jù)庫的同時,推送到粉絲的收件箱
  • 收件箱滿足可以根據(jù)時間戳排序,必須用Redis的數(shù)據(jù)結構實現(xiàn)
  • 查詢收件箱數(shù)據(jù)時,可以實現(xiàn)分頁查詢

分頁查詢方案

Feed流中的數(shù)據(jù)會不斷更新,所以數(shù)據(jù)的角標也在變化,因此不能采用傳統(tǒng)的分頁模式。

傳統(tǒng)了分頁在feed流是不適用的,因為我們的數(shù)據(jù)會隨時發(fā)生變化

假設在t1 時刻,我們去讀取第一頁,此時page = 1 ,size = 5 ,那么我們拿到的就是10~6 這幾條記錄,假設現(xiàn)在t2時候又發(fā)布了一條記錄,此時t3 時刻,我們來讀取第二頁,讀取第二頁傳入的參數(shù)是page=2 ,size=5 ,那么此時讀取到的第二頁實際上是從6 開始,然后是6~2 ,那么我們就讀取到了重復的數(shù)據(jù),所以feed流的分頁,不能采用原始方案來做。

Feed流的滾動分頁

我們需要記錄每次操作的最后一條,然后從這個位置開始去讀取數(shù)據(jù)

舉個例子:我們從t1時刻開始,拿第一頁數(shù)據(jù),拿到了10~6,然后記錄下當前最后一次拿取的記錄,就是6,t2時刻發(fā)布了新的記錄,此時這個11放到最頂上,但是不會影響我們之前記錄的6,此時t3時刻來拿第二頁,第二頁這個時候拿數(shù)據(jù),還是從6后一點的5去拿,就拿到了5-1的記錄。我們這個地方可以采用sortedSet來做,可以進行范圍查詢,并且還可以記錄當前獲取數(shù)據(jù)時間戳最小值,就可以實現(xiàn)滾動分頁了,11這條新插入的數(shù)據(jù)進行上拉刷新的時候就會刷新,不必擔心漏讀

分頁演示

首先創(chuàng)建一個Zset集合

通過這一命令我們可以通過score值降序排的方式取出前三條記錄,這么看似乎沒有問題,我們下一頁要查詢的就是后四條記錄,但feed流的數(shù)據(jù)是不斷更新,恰好此時set集合中加入m8

zrevrange z1 0 2 withscores

后三條記錄的查詢理論上是再查三條,這時候按照角標查m5就已經重復查詢了,這在業(yè)務中是不允許的

 按照score值來查就可以避免該問題,我們只需要記住上一次查詢的最小分數(shù),讓它在下次查詢時最為max,即可實現(xiàn)分頁查詢,limit 后面的第一個參數(shù)即是偏移量,0表示要包含max,1表示小于max分數(shù)的下一個元素

zrevrangebyscore z1 6 0 withscores limit 1 3

推送收件箱

當用戶發(fā)送完筆記后,也要將筆記推送粉絲的收件箱中,即用戶id為key的zest集合中

@Override
public Result saveBlog(Blog blog) {
    // 1.獲取登錄用戶
    UserDTO user = UserHolder.getUser();
    blog.setUserId(user.getId());
    // 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", user.getId()).list();
    // 4.推送筆記id給所有粉絲
    for (Follow follow : follows) {
        // 4.1.獲取粉絲id
        Long userId = follow.getUserId();
        // 4.2.推送
        String key = FEED_KEY + userId;
        stringRedisTemplate.opsForZSet().add(key, blog.getId().toString(), System.currentTimeMillis());
    }
    // 5.返回id
    return Result.ok(blog.getId());
}

實現(xiàn)分頁查詢收郵箱

在個人主頁的“關注”卡片中,查詢并展示推送的Blog信息:

具體操作如下:

1、每次查詢完成后,我們要分析出查詢出數(shù)據(jù)的最小時間戳,這個值會作為下一次查詢的條件,即下一次查詢的最大時間戳

2、我們需要找到與上一次查詢相同的查詢個數(shù)作為偏移量,下次查詢時,跳過這些查詢過的數(shù)據(jù),拿到我們需要的數(shù)據(jù)

綜上:我們的請求參數(shù)中就需要攜帶 lastId:上一次查詢的最小時間戳和偏移量這兩個參數(shù)。

這兩個參數(shù)第一次會由前端來指定,以后的查詢就根據(jù)后臺結果作為條件,再次傳遞到后臺。

 

定義返回實體類

@Data
public class ScrollResult {
    private List<?> list;
    private Long minTime;
    private Integer offset;
}

BlogController

注意:RequestParam 表示接受url地址欄傳參的注解,當方法上參數(shù)的名稱和url地址欄不相同時,可以通過RequestParam 來進行指定

@GetMapping("/of/follow")
public Result queryBlogOfFollow(
    @RequestParam("lastId") Long max, @RequestParam(value = "offset", defaultValue = "0") Integer offset){
    return blogService.queryBlogOfFollow(max, offset);
}

BlogServiceImpl  

@Override
public Result queryBlogOfFollow(Long max, Integer offset) {
    // 1.獲取當前用戶
    Long userId = UserHolder.getUser().getId();
    // 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.解析數(shù)據(jù):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.獲取分數(shù)(時間戳)
        long time = tuple.getScore().longValue();
        if(time == minTime){
            os++;
        }else{
            minTime = time;
            os = 1;
        }
    }
	os = minTime == max ? os : os + offset;
    // 5.根據(jù)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);
}

到此這篇關于redis實現(xiàn)好友關注&消息推送的方法示例的文章就介紹到這了,更多相關redis 好友關注&消息推送內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 詳解Redis數(shù)據(jù)結構之跳躍表

    詳解Redis數(shù)據(jù)結構之跳躍表

    這篇文章主要介紹了Redis數(shù)據(jù)結構中的跳躍表的相關知識,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-11-11
  • RabbitMQ+redis+Redisson分布式鎖+seata實現(xiàn)訂單服務的流程分析

    RabbitMQ+redis+Redisson分布式鎖+seata實現(xiàn)訂單服務的流程分析

    訂單服務涉及許多方面,分布式事務,分布式鎖,例如訂單超時未支付要取消訂單,訂單如何防止重復提交,如何防止超賣、這里都會使用到,這篇文章主要介紹了RabbitMQ+redis+Redisson分布式鎖+seata實現(xiàn)訂單服務的流程分析,需要的朋友可以參考下
    2024-07-07
  • 詳解redis集群選舉機制

    詳解redis集群選舉機制

    這篇文章主要介紹了詳解redis集群選舉機制,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-03-03
  • Redis分布式鎖與Redlock算法實現(xiàn)

    Redis分布式鎖與Redlock算法實現(xiàn)

    在Redis中,可以使用多種方式實現(xiàn)分布式鎖,如使用SETNX命令或RedLock算法,本文就來介紹一下Redis分布式鎖與Redlock算法實現(xiàn),感興趣的可以了解一下
    2023-12-12
  • 基于Redis自動過期的流處理暫停機制

    基于Redis自動過期的流處理暫停機制

    基于Redis自動過期的流處理暫停機制是一種高效、可靠且易于實現(xiàn)的解決方案,防止延時過大的數(shù)據(jù)影響實時處理自動恢復處理,以避免積壓的數(shù)據(jù)影響實時性,下面就來詳細的介紹一下
    2025-08-08
  • 淺談Redis中的RDB快照

    淺談Redis中的RDB快照

    雖說Redis是內存數(shù)據(jù)庫,但是它為數(shù)據(jù)的持久化提供了兩個技術,分別是AOF日志和RDB快照。這兩種技術都會用各用一個日志文件來記錄信息,但是記錄的內容是不同的。AOF 文件的內容是操作命令; RDB 文件的內容是二進制數(shù)據(jù)。本文將討論RDB快照的原理和使用
    2021-06-06
  • Redis集群刪除后重建后報錯:unrecoverable erro:corrupted cluster config file問題及解決

    Redis集群刪除后重建后報錯:unrecoverable erro:corrupted clust

    這篇文章主要介紹了Redis集群刪除后重建后報錯:unrecoverable erro:corrupted cluster config file問題及解決,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2026-06-06
  • Redis過期事件監(jiān)聽器的完整實現(xiàn)步驟

    Redis過期事件監(jiān)聽器的完整實現(xiàn)步驟

    要使用 Redis 過期事件監(jiān)聽器來更新數(shù)據(jù)庫狀態(tài),我們需要確保 Redis 的事件通知已啟用,并實現(xiàn)監(jiān)聽器來捕獲過期的鍵,并根據(jù)需要更新數(shù)據(jù)庫,本文給大家介紹了Redis過期事件監(jiān)聽器的完整實現(xiàn)步驟,需要的朋友可以參考下
    2024-10-10
  • Caffeine結合Redis空值緩存實現(xiàn)多級緩存

    Caffeine結合Redis空值緩存實現(xiàn)多級緩存

    本文介紹了SpringBoot整合Caffeine本地緩存+Redis分布式緩存+空值緩存的三級緩存方案,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2026-01-01
  • redis的主從配置方法詳解

    redis的主從配置方法詳解

    今天為大家介紹下linux系統(tǒng)下redis的主從配置方法,Linux系統(tǒng)下的redis的主從配置方法非常簡單下面是具體的操作步驟
    2018-09-09

最新評論

淳化县| 阳信县| 灌南县| 林甸县| 安吉县| 梅州市| 卢氏县| 威信县| 新津县| 郸城县| 舞钢市| 邻水| 宜章县| 应城市| 石家庄市| 辽中县| 昭通市| 平原县| 台南县| 铜川市| 望奎县| 兴安县| 兴城市| 平舆县| 华亭县| 东明县| 西宁市| 钟祥市| 庆城县| 兴国县| 都匀市| 澄城县| 顺平县| 绩溪县| 苍山县| 南召县| 于都县| 吉林省| 岳阳市| 临湘市| 大丰市|