欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Redis 實(shí)現(xiàn)好友關(guān)注和關(guān)注推送的示例代碼

 更新時(shí)間:2025年03月11日 10:20:56   作者:快樂的小三菊  
本文介紹了使用Redis實(shí)現(xiàn)好友關(guān)注和關(guān)注推送功能,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

一、關(guān)注和取關(guān)

1.1 簡介

在探店圖文的詳情頁面中,可以關(guān)注發(fā)布筆記的作者:

1.2 需求描述

實(shí)現(xiàn)兩個(gè)接口:關(guān)注和取關(guān)接口、判斷是否關(guān)注的接口

1.3 代碼實(shí)現(xiàn)

涉及到的 controller 層代碼如下:

@RestController
@RequestMapping("/follow")
public class FollowController {

    @Resource
    private IFollowService followService;

    @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);
    }
}

涉及到的 service 層代碼如下:

@Service
public class FollowServiceImpl extends ServiceImpl<FollowMapper, Follow> implements IFollowService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private IUserService userService;

    @Override
    public Result follow(Long followUserId, Boolean isFollow) {
        // 1.獲取登錄用戶
        Long userId = UserHolder.getUser().getId();
        String key = "follows:" + userId;
        // 1.判斷到底是關(guān)注還是取關(guān)
        if (isFollow) {
            // 2.關(guān)注,新增數(shù)據(jù)
            Follow follow = new Follow();
            follow.setUserId(userId);
            follow.setFollowUserId(followUserId);
            boolean isSuccess = save(follow);
            if (isSuccess) {
                // 把關(guān)注用戶的id,放入redis的set集合 sadd userId followerUserId
                stringRedisTemplate.opsForSet().add(key, followUserId.toString());
            }
        } else {
            // 3.取關(guān),刪除 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) {
                // 把關(guān)注用戶的id從Redis集合中移除
                stringRedisTemplate.opsForSet().remove(key, followUserId.toString());
            }
        }
        return Result.ok();
    }

    @Override
    public Result isFollow(Long followUserId) {
        // 1.獲取登錄用戶
        Long userId = UserHolder.getUser().getId();
        // 2.查詢是否關(guān)注 select count(*) from tb_follow where user_id = ? and follow_user_id = ?
        Integer count = query().eq("user_id", userId).eq("follow_user_id", followUserId).count();
        // 3.判斷
        return Result.ok(count > 0);
    }
}

二、共同關(guān)注

2.1 簡介

點(diǎn)擊博主頭像,可以進(jìn)入博主首頁:

2.2 需求描述

利用 Redis 中恰當(dāng)?shù)臄?shù)據(jù)結(jié)構(gòu),實(shí)現(xiàn)共同關(guān)注功能。在博主個(gè)人頁面展示出當(dāng)前用戶與博主的共同好友。

2.3 代碼實(shí)現(xiàn)

涉及到的 controller 層代碼如下:

@RestController
@RequestMapping("/follow")
public class FollowController {

    @Resource
    private IFollowService followService;

    @GetMapping("/common/{id}")
    public Result followCommons(@PathVariable("id") Long id){
        return followService.followCommons(id);
    }
}

涉及到的 service 層代碼如下:

@Service
public class FollowServiceImpl extends ServiceImpl<FollowMapper, Follow> implements IFollowService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private IUserService userService;

    @Override
    public Result followCommons(Long id) {
        // 1.獲取當(dāng)前用戶
        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);
    }
}

三、關(guān)注推送

3.1 簡介

關(guān)注推送也叫做 Feed 流,直譯為投喂。為用戶持續(xù)的提供 “沉浸式” 的體驗(yàn),通過無限下拉刷新獲取新的信息。

3.2 Feed 流的模式

3.2.1 TimeLine

不做內(nèi)容篩選,簡單的按照內(nèi)容發(fā)布時(shí)間排序,常用于好友或關(guān)注。例如朋友圈。

優(yōu)點(diǎn):信息全面,不會(huì)有缺失。并且實(shí)現(xiàn)也相對(duì)簡單

缺點(diǎn):信息噪音較多,用戶不一定感興趣,內(nèi)容獲取效率低

3.2.2 智能排序

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

優(yōu)點(diǎn):投喂用戶感興趣信息,用戶粘度很高,容易沉迷

缺點(diǎn):如果算法不精準(zhǔn),可能起到反作用

3.3 實(shí)現(xiàn)方案

本例中的個(gè)人頁面,是基于關(guān)注的好友來做 Feed 流,因此采用 Timeline 的模式。該模式的實(shí)現(xiàn)方案有三種:拉模式、推模式、推拉結(jié)合模式。

3.3.1 拉模式

拉模式也叫做讀擴(kuò)散,假設(shè)現(xiàn)在有三個(gè) up 主,張三李四和王五,它們?nèi)齻€(gè)人將來會(huì)發(fā)一些消息,此時(shí),給他們每個(gè)人都準(zhǔn)備一個(gè)發(fā)件箱,將來它們發(fā)送消息的時(shí)候就會(huì)發(fā)送到發(fā)件箱里面去,發(fā)送的消息除了本身以外還需要攜帶一個(gè)時(shí)間戳,因?yàn)楹竺嬉凑諘r(shí)間排序。

此時(shí)有一個(gè)粉絲趙六,它有一個(gè)收件箱,平常是空的,只有他要去讀消息的時(shí)候我們才會(huì)給他去拉取,即從它所關(guān)注的 up 主的發(fā)件箱去拉取消息,拉過來之后按照時(shí)間去排序,這樣就可以去讀取了。

這種模式的優(yōu)點(diǎn)是節(jié)省內(nèi)存空間,因?yàn)槭占渥x取完畢之后就可以清空,下次再重新拉取。缺點(diǎn)是每次讀消息的時(shí)候都要重新去拉取發(fā)件箱的消息,然后再做排序,這一系列的動(dòng)作耗時(shí)會(huì)比較久,讀取的延遲較高。 

3.3.2 推模式

推模式也叫寫擴(kuò)散。假設(shè)此時(shí)有兩個(gè) up 主,張三和李四,粉絲1關(guān)注了張三、粉絲2關(guān)注了張三和李四、粉絲3也關(guān)注了張三和李四,假設(shè)此時(shí)張三要發(fā)送消息,它所發(fā)送的消息會(huì)直接推送到所有粉絲的收件箱里面去,然后對(duì)收件箱里面的消息進(jìn)行排序,此時(shí)粉絲可以直接從收件箱里面讀取消息

此種模式的優(yōu)點(diǎn)是延遲非常的低,彌補(bǔ)了拉模式的缺點(diǎn)。缺點(diǎn)是由于沒有了發(fā)件箱,不得不把消息發(fā)送給每一個(gè)粉絲,內(nèi)存占用會(huì)很高,即一個(gè)消息要寫N份 

3.3.3 推拉結(jié)合模式

推拉結(jié)合模式也叫做讀寫混合,兼具推和拉兩種模式的優(yōu)點(diǎn)。假設(shè)現(xiàn)在有一個(gè)大 V 和普通人張三,還有兩個(gè)普通粉絲和一個(gè)活躍粉絲,每個(gè)粉絲都有自己的收件箱。假設(shè)此時(shí)普通人張三要發(fā)送消息,他的粉絲很少,就可以采用推模式,即直接將消息推送到每一個(gè)粉絲的收件箱里面。

而大 V 則不一樣,大 的粉絲很多,雖然粉絲多,但是粉絲存在差異,有活躍粉絲和一般粉絲。針對(duì)于活躍粉絲采用推模式,而針對(duì)于一般粉絲采取拉模式,因?yàn)榇?strong> V 需要有一個(gè)發(fā)件箱。

這種推拉結(jié)合的模式,既節(jié)省了內(nèi)存又照顧了活躍用戶的感受

3.3.4 總結(jié)

3.4 Feed 流的分頁問題

Feed 流中的數(shù)據(jù)會(huì)不斷更新,所以數(shù)據(jù)的角標(biāo)也在變化,因此不能采用傳統(tǒng)的分頁模式。接下來我們分析下原因。

以下圖為例,橫線代表時(shí)間軸,此時(shí)時(shí)間來到了 t1 時(shí)刻,收件箱里面已經(jīng)有了 10 條消息了,數(shù)字越大代表越新,讀取第一頁數(shù)據(jù)沒啥問題。

此時(shí)來到了 t2 時(shí)刻,有人插入了一條新的數(shù)據(jù),此時(shí)我們的 11 數(shù)據(jù)就跑到了最前面去了

等到來到了 t3 時(shí)刻,此時(shí)需要讀取第二頁的內(nèi)容,此時(shí)就會(huì)出現(xiàn)重復(fù)讀取的問題,分頁就出現(xiàn)了混亂。

這就是 Feed 流不能采用傳統(tǒng)的分頁模式的原因。

3.5 Feed 流的滾動(dòng)分頁

此時(shí)就需要采用 Feed 流的滾動(dòng)分頁,即記錄每次分頁的最后一條,下次再從這個(gè)位置開始查。第一次讀取時(shí) lastId 設(shè)置成無窮大就可以了,如下圖:

到了 t2 時(shí)刻有人插入了新的數(shù)據(jù) 11,如下圖:

等到 t3 時(shí)刻,讀取第二頁的時(shí)候,讓 lastId = 6,向后查 條就不會(huì)出現(xiàn)問題了,如下圖:

此時(shí)的查詢是不依賴于腳標(biāo)的,所以數(shù)據(jù)不受影響。所以只能使用 zset 結(jié)構(gòu)。

3.6 需求描述

基于推模式實(shí)現(xiàn)關(guān)注推送功能:

1、修改新增探店筆記的業(yè)務(wù),在保存 blog 到數(shù)據(jù)庫的同時(shí),推送到粉絲的收件箱

2、收件箱滿足可以根據(jù)時(shí)間戳排序,必須用 Redis 的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)

3、查詢收件箱數(shù)據(jù)時(shí),可以實(shí)現(xiàn)分頁查詢

3.7 代碼實(shí)現(xiàn)

首先完成修改新增探店筆記的業(yè)務(wù),在保存 blog 到數(shù)據(jù)庫的同時(shí),推送到粉絲的收件箱功能,涉及到的 controller 層代碼如下:

@RestController
@RequestMapping("/blog")
public class BlogController {

    @Resource
    private IBlogService blogService;

    @PostMapping
    public Result saveBlog(@RequestBody Blog blog) {
        return blogService.saveBlog(blog);
    }
}

涉及到的 service 層代碼如下:

@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {

    @Resource
    private IUserService userService;

    @Resource
    StringRedisTemplate stringRedisTemplate;

    @Resource
    IFollowService followService;

    @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:" + userId;
            stringRedisTemplate.opsForZSet().add(key, blog.getId().toString(), System.currentTimeMillis());
        }
        // 5.返回id
        return Result.ok(blog.getId());
    }
	
}

接下來完成剩下的兩小點(diǎn)需求,界面請(qǐng)求的參數(shù)如下:

涉及到的 controller 層代碼如下:

@RestController
@RequestMapping("/blog")
public class BlogController {

    @Resource
    private IBlogService blogService;

    @PostMapping
    public Result saveBlog(@RequestBody Blog blog) {
        return blogService.saveBlog(blog);
    }

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

涉及到的 service 層代碼如下:

    @Override
    public Result queryBlogOfFollow(Long max, Integer offset) {
        // 1.獲取當(dāng)前用戶
        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(時(shí)間戳)、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.獲取分?jǐn)?shù)(時(shí)間戳)
            long time = tuple.getScore().longValue();
            if(time == minTime){
                os++;
            }else{
                minTime = time;
                os = 1;
            }
        }
        // 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有關(guān)的用戶
            queryBlogUser(blog);
            // 5.2.查詢blog是否被點(diǎn)贊
            isBlogLiked(blog);
        }

        // 6.封裝并返回
        ScrollResult r = new ScrollResult();
        r.setList(blogs);
        r.setOffset(os);
        r.setMinTime(minTime);

        return Result.ok(r);
    }

封裝返回給前端的實(shí)體類如下:

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

到此這篇關(guān)于 Redis 實(shí)現(xiàn)好友關(guān)注和關(guān)注推送的示例代碼的文章就介紹到這了,更多相關(guān) Redis 好友關(guān)注和關(guān)注推送內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家! 

相關(guān)文章

  • Govern Service 基于 Redis 的服務(wù)治理平臺(tái)安裝過程詳解

    Govern Service 基于 Redis 的服務(wù)治理平臺(tái)安裝過程詳解

    Govern Service 是一個(gè)輕量級(jí)、低成本的服務(wù)注冊(cè)、服務(wù)發(fā)現(xiàn)、 配置服務(wù) SDK,通過使用現(xiàn)有基礎(chǔ)設(shè)施中的 Redis 不用給運(yùn)維部署帶來額外的成本與負(fù)擔(dān),接下來通過本文給大家分享Govern Service 基于 Redis 的服務(wù)治理平臺(tái)的相關(guān)知識(shí),感興趣的朋友一起看看吧
    2021-05-05
  • Redis精確去重計(jì)數(shù)方法(咆哮位圖)

    Redis精確去重計(jì)數(shù)方法(咆哮位圖)

    這篇文章主要給大家介紹了關(guān)于Redis精確去重計(jì)數(shù)方法(咆哮位圖)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Redis具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06
  • redis連接報(bào)錯(cuò)error:NOAUTH Authentication required

    redis連接報(bào)錯(cuò)error:NOAUTH Authentication required

    本文主要介紹了redis連接報(bào)錯(cuò)error:NOAUTH Authentication required,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-05-05
  • Redis憑啥可以這么快

    Redis憑啥可以這么快

    本文詳細(xì)的介紹了為啥使用Redis的時(shí)候,可以做到非常快的讀取速度,對(duì)于大家學(xué)習(xí)Redis非常有幫助,希望大家喜歡
    2021-02-02
  • redis中使用bloomfilter的白名單功能解決緩存穿透問題

    redis中使用bloomfilter的白名單功能解決緩存穿透問題

    本文主要介紹了redis中使用bloomfilter的白名單功能解決緩存穿透問題,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • Redis 中的布隆過濾器的實(shí)現(xiàn)

    Redis 中的布隆過濾器的實(shí)現(xiàn)

    這篇文章主要介紹了Redis 中的布隆過濾器的實(shí)現(xiàn),詳細(xì)的介紹了什么是布隆過濾器以及如何實(shí)現(xiàn),非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2018-10-10
  • Redis 使用跳表實(shí)現(xiàn)有序集合的方法

    Redis 使用跳表實(shí)現(xiàn)有序集合的方法

    Redis有序集合底層為什么使用跳表而非其他數(shù)據(jù)結(jié)構(gòu)如平衡樹、紅黑樹或B+樹的原因在于其特殊的設(shè)計(jì)和應(yīng)用場景,跳表提供了與平衡樹類似的效率,同時(shí)實(shí)現(xiàn)更簡單,調(diào)試和修改也更加容易,感興趣的朋友一起看看吧
    2024-09-09
  • 巧用Redis實(shí)現(xiàn)分布式鎖詳細(xì)介紹

    巧用Redis實(shí)現(xiàn)分布式鎖詳細(xì)介紹

    大家好,本篇文章主要講的是巧用Redis實(shí)現(xiàn)分布式鎖詳細(xì)介紹,感興趣的同學(xué)趕快來看一看吧,對(duì)你有幫助的話記得收藏一下,方便下次瀏覽
    2021-12-12
  • RediSearch加RedisJSON大于Elasticsearch的搜索存儲(chǔ)引擎

    RediSearch加RedisJSON大于Elasticsearch的搜索存儲(chǔ)引擎

    這篇文章主要為大家介紹了RediSearch加RedisJSON大于Elasticsearch的王炸使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-07-07
  • 淺談Redis處理接口冪等性的兩種方案

    淺談Redis處理接口冪等性的兩種方案

    本文主要介紹了淺談Redis處理接口冪等性的兩種方案,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08

最新評(píng)論