Redis框架(十五):大众点评项目 共同关注方案实现?双指针筛选DB数据:Redis取交集

news2025/2/28 21:42:57

大众点评项目 好友关注 共同关注

  • 需求:好友关注 共同关注
  • 业务逻辑展示
    • 点击关注功能实现
    • 判断当前用户是否关注了此博主
    • 共同好友列表查询
  • 业务逻辑实现
    • 双指针筛选DB数据
    • Redis取交集
  • 总结

SpringCloud章节复习已经过去,新的章节Redis开始了,这个章节中将会回顾Redis实战项目 大众点评
主要依照以下几个原则

  1. 基础+实战的Demo和Coding上传到我的代码仓库
  2. 在原有基础上加入一些设计模式,stream+lamdba等新的糖
  3. 通过DeBug调试,进入组件源码去分析底层运行的规则和设计模式

代码会同步在我的gitee中去,觉得不错的同学记得一键三连求关注,感谢:
Redis优化-链接: RedisCommonFollowProject

需求:好友关注 共同关注

成果展示:共同关注列表查询

在这里插入图片描述

这里给出两种方案,

  1. DB查询的话,两次数据库查询IO,并且可以通过基本的stream流处理数据,进行排序等处理,通过双指针筛选,保证时间复杂度为N
  2. Redis取交集,本身就是缓存数据库,通过封装的API操作,查询

后面可以通过通过一些压测工具,JMX进行对比和梳理,先天结构上猜测,在大部分情况下,Redis应该是更快的那个

业务逻辑展示

点击关注功能实现

在这里插入图片描述

判断当前用户是否关注了此博主

在这里插入图片描述

共同好友列表查询

在这里插入图片描述

业务逻辑实现

双指针筛选DB数据

** Controller层 - 实现 **

 * @PathVariable主要用于接收http://host:port/path/{参数值}数据。
 * @RequestParam主要用于接收http://host:port/path?参数名=参数值数据,这里后面也可以不跟参数值。
@RestController
@RequestMapping("/blog")
public class BlogController {

    @Resource
    private IBlogService blogService;


    @PostMapping
    public Result saveBlog(@RequestBody Blog blog) {
        // 获取登录用户
        UserDTO user = UserHolder.getUser();
        blog.setUserId(user.getId());
        // 保存探店博文
        blogService.save(blog);
        // 返回id
        return Result.ok(blog.getId());
    }

    @PutMapping("/like/{id}")
    public Result likeBlog(@PathVariable("id") Long id) {
        // 修改点赞数量
        return blogService.likeBlog(id);
    }

    @GetMapping("/of/me")
    public Result queryMyBlog(@RequestParam(value = "current", defaultValue = "1") Integer current) {
        // 获取登录用户
        UserDTO user = UserHolder.getUser();
        // 根据用户查询
        Page<Blog> page = blogService.query()
                .eq("user_id", user.getId()).page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
        // 获取当前页数据
        List<Blog> records = page.getRecords();
        return Result.ok(records);
    }

    @GetMapping("/hot")
    public Result queryHotBlog(@RequestParam(value = "current", defaultValue = "1") Integer current) {
        return blogService.queryHotBlog(current);
    }

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


    @GetMapping("/likes/{id}")
    public Result likesBlog(@PathVariable("id") Long id) {
        // 修改点赞数量
        return blogService.queryBlogLikes(id);
    }


    /**
     * @RequestParam与@PathVariable为spring的注解,都可以用于在Controller层接收前端传递的数据,不过两者的应用场景不同。
     *
     * @PathVariable主要用于接收http://host:port/path/{参数值}数据。
     * @RequestParam主要用于接收http://host:port/path?参数名=参数值数据,这里后面也可以不跟参数值。
     * @param current
     * @param id
     * @return
     */
    @GetMapping("/of/user")
    public Result queryBlogByUserId(@RequestParam(value = "current", defaultValue = "1") Integer current,
                                    @RequestParam("id") Long id){
        return blogService.queryBlogByUserId(current, id);
    }

}

** Service层 - 实现 **


@Slf4j
@Service
public class FollowServiceImpl extends ServiceImpl<FollowMapper, Follow> implements IFollowService {

    @Resource
    private IUserService userService;

    @Override
    public Result followJudge(Long id, Boolean isFollow) {
        UserDTO user = UserHolder.getUser();
        if(isFollow){
            Follow follow = new Follow();
            follow.setUserId(user.getId()).setFollowUserId(id);
            save(follow);
        }else{
//            removeById(new QueryWrapper<Follow>().eq("user_id", user.getId())
//                                                    .eq("follow_user_id", id));
            log.debug("QueryChainWrapper" + query().select("id").eq("user_id", user.getId()).eq("follow_user_id", id));
            log.debug("Integer count = " + query().count());
            remove(new QueryWrapper<Follow>().eq("user_id", user.getId())
                                                    .eq("follow_user_id", id));
        }
        return Result.ok();
    }

    @Override
    public Result isFollowJudge(Long id) {
        UserDTO user = UserHolder.getUser();

        Integer idNum = query().eq("user_id", user.getId())
                .eq("follow_user_id", id).count();

        return Result.ok(idNum > 0);
    }

共同好友功能实现;

  • 双指针

       判断思想:       有序 数组A{ 1 5 8 15} B{ 8 9  15  17 22}
       逻辑判断大小, 选取A中最小的和B对比, 1<8  5 < 8 8=8 指针A 左移动,指针B左移,
        15 》9,  指针B左移,直至两边指针有一个结束为止
    

    /**
     * @Function 共同好友
     * @param blogHostId
     * @return
     */
    @Override
    public Result isCommonFollow(Long blogHostId) {
        Long userId = UserHolder.getUser().getId();

        if(userId==null){
            return Result.fail("需要登录后进行");
        }

        List<Long> userFollowList = query().eq("user_id", userId).list()
                .stream().map(s->s.getFollowUserId()).sorted().collect(Collectors.toList());
        List<Long> hostFollowList = query().eq("user_id", blogHostId).list()
                .stream().map(s->s.getFollowUserId()).sorted().collect(Collectors.toList());

        List<Long> commonFollowList = new ArrayList<>();

        /**
         * 没啥暴力呗,两个For,直接n*n
         */
/*        userFollowList.stream().forEach(s->{
            for (int i = 0; i < hostFollowList.size(); i++) {
                //这里遍历的时间复杂度是n*n,我们可以使用双指针法,可以使时间复杂度 缩短为 n
                if(s.equals(hostFollowList.get(i))){
                    commonFollowList.add(s);
                    break;
                }
            }
        });*/

        /**
         * 判断思想:       有序 数组A{ 1 5 8 15} B{ 8 9  15  17 22}
         * 逻辑判断大小, 选取A中最小的和B对比, 1<8  5 < 8 8=8 指针A 左移动,指针B左移,
         * 15 》9,  指针B左移,直至两边指针有一个结束为止
         */
        int pointA = 0, pointB = 0;
        while(pointA<userFollowList.size() && pointB<hostFollowList.size()){
            if(userFollowList.get(pointA).equals(hostFollowList.get(pointB))){
                commonFollowList.add(userFollowList.get(pointA));
                pointA++;
                pointB++;
            }else if(userFollowList.get(pointA)<hostFollowList.get(pointB)){
                pointA++;
            }else{
                pointB++;
            }
        }

        List<UserDTO> userList = commonFollowList.stream().map(id -> {
            User user = userService.query().eq("id", id).one();
            return BeanUtil.copyProperties(user, UserDTO.class);
        }).collect(Collectors.toList());

        if (userList == null) {
            return Result.ok("没有共同关注的好友");
        }

        return Result.ok(userList);
    }
}

Redis取交集

存取类型:Set
通过Redis相关API:intersect

    @Override
    public Result followJudge(Long id, Boolean isFollow) {

        //id:博主ID  userId:当前账号ID
        UserDTO user = UserHolder.getUser();
        if(user==null){
            return Result.fail("尚未登录");
        }
        String key = RedisConstants.FOLLOW_ID + user.getId();
        if(isFollow){
            Follow follow = new Follow();
            follow.setUserId(user.getId()).setFollowUserId(id);
            boolean isSave = save(follow);
            if (isSave) {
                stringRedisTemplate.opsForSet().add(key, id.toString());
            }
        }else{
//            removeById(new QueryWrapper<Follow>().eq("user_id", user.getId())
//                                                    .eq("follow_user_id", id));
            log.debug("QueryChainWrapper" + query().select("id").eq("user_id", user.getId()).eq("follow_user_id", id));
            log.debug("Integer count = " + query().count());
            boolean isRemove = remove(new QueryWrapper<Follow>().eq("user_id", user.getId())
                    .eq("follow_user_id", id));
            if (isRemove) {
                stringRedisTemplate.opsForSet().remove(key, id.toString());
            }

        }

        return Result.ok();
    }

在这里插入图片描述

    /**
     * @Function 共同好友
     * @param blogHostId
     * @return
     */
    @Override
    public Result isCommonFollow(Long blogHostId) {
        Long userId = UserHolder.getUser().getId();
        String userKey = RedisConstants.FOLLOW_ID + userId;
        String blogHostKey = RedisConstants.FOLLOW_ID + blogHostId;

        Set<String> intersect = stringRedisTemplate.opsForSet().intersect(userKey, blogHostKey);

        if (intersect == null || intersect.isEmpty()) {
            return Result.ok(Collections.emptyList());
        }

        List<Long> idList = intersect.stream().map(Long::valueOf).collect(Collectors.toList());

        List<UserDTO> userList = userService.listByIds(idList).stream().map(user -> {
            return BeanUtil.copyProperties(user, UserDTO.class);
        }).collect(Collectors.toList());

        return Result.ok(userList);
    }

总结两个API使用:

求交集 stringRedisTemplate.opsForSet().intersect(userKey, blogHostKey);
set存数据:stringRedisTemplate.opsForSet().add(key, id.toString());
set删除数据: stringRedisTemplate.opsForSet().remove(key, id.toString()); }

MP的查询构造器:remove(new QueryWrapper<Follow>().eq("user_id", user.getId()) .eq("follow_user_id", id));

总结

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/94597.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

字节一面:select......for update会锁表还是锁行?

select查询语句是不会加锁的&#xff0c;但是select .......for update除了有查询的作用外&#xff0c;还会加锁呢&#xff0c;而且它是悲观锁。 那么它加的是行锁还是表锁&#xff0c;这就要看是不是用了索引/主键。 没用索引/主键的话就是表锁&#xff0c;否则就是是行锁。…

sklearn基础篇(十)-- 非负矩阵分解与t-SNE

1 非负矩阵分解(NFM) NMF(Non-negative matrix factorization)&#xff0c;即对于任意给定的一个非负矩阵V\pmb{V}VVV&#xff0c;其能够寻找到一个非负矩阵W\pmb{W}WWW和一个非负矩阵H\pmb{H}HHH&#xff0c;满足条件VW∗H\pmb{VW*H}VW∗HVW∗HVW∗H,从而将一个非负的矩阵分解…

物联网架构实例—解决Linux(Ubuntu)服务器最大TCP连接数限制

1.前言&#xff1a; 在对物联网网关进行压测的时候&#xff0c;发现在腾讯云部署网关程序&#xff0c;设备接入数量只能达到4000多个长连接&#xff0c;之后就再也无法接入终端了。 之前在阿里云部署的时候明明可以到达2万左右&#xff0c;而且腾讯云的这个服务器比阿里云的硬…

蓝桥杯嵌入式综合实验真题-联网电压测量系统设计与实现

目录 实验要求&#xff1a; 实验思路&#xff1a; 核心代码&#xff1a; &#xff08;1&#xff09;变量声明 &#xff08;2&#xff09;函数声明 &#xff08;3&#xff09;main主函数 &#xff08;4&#xff09;按键&#xff08;长按/短按&#xff09; &#xff08;5&…

K8s——Service、代理模式演示(二)

Service 演示 SVC 创建svc-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata:name: myapp-deploynamespace: default spec:replicas: 3selector:matchLabels:app: myapprelease: stabeltemplate:metadata:labels:app: myapprelease: stabelenv: testspec: c…

学习UI设计有哪些figma插件

自2016年推出以来&#xff0c;Figma已发展成为市场领先者UI设计工具之一。 因为它不仅简单易用&#xff0c;功能优秀&#xff0c;而且基于云服务&#xff0c;可以实时编辑&#xff0c;节省大量手动下载或复制文件的时间。不仅如此&#xff0c;Figma还提供合作功能&#xff0c;…

一文读懂PFMEA(过程失效模式及后果分析)

PFMEA是过程失效模式及后果分析(Process Failure Mode andEffects Analysis)的英文简称&#xff0c;是由负责制造/装配的工程师/小组主要采用的一种分析技术&#xff0c;用以最大限度地保证各种潜在的失效模式及其相关的起因/机理已得到充分的考虑和论述。 名词解释&#xff1a…

springboot介绍

笔记来源于 动力节点springboot Javaconfig xml方式 在创建模块时&#xff0c;idea2022新版选择internal即可&#xff1a; pom.xml文件 <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xs…

圆顶光源特点及应用——塑料包装袋、PCB板检测

照明系统是机器视觉系统较为关键的部分之一&#xff0c;机器视觉光源直接影响到图像的质量&#xff0c;进而影响到系统的性能。其重要性无论如何强调都是不过分的。好的打光设计能够使我们得到一幅好的图象&#xff0c;从而改善整个整个系统的分辨率&#xff0c;简化软件的运算…

Java JNA 调用DLL(动态连接库) 回调函数

首先准备好动态链接库dll 参考连接 visual studio 2017 创建dll文件并使用https://blog.csdn.net/miss_na/article/details/113524280 Visual Studio 2017 动态链接库(.dll)生成与使用的简明教程https://blog.csdn.net/Hide_on_Stream/article/details/109172054 jni之jni与…

【记录】U盘安装Ubuntu20.04系统

之前电脑安装的Centos7系统&#xff0c;但是在启动过程中遇到了文件异常&#xff0c;就开不了机了&#xff0c;另外貌似Centos7已经停止维护了&#xff0c;想了下&#xff0c;果断不要数据了&#xff0c;直接重装系统吧&#xff0c;这次选用的是Ubuntu 20.04【ps: 没有选择最新…

「含元量」激增,这届世界杯的看点不止足球

文|智能相对论 作者|青月 半决赛结束&#xff0c;卡塔尔世界杯已经正式进入倒计时阶段。 这届世界杯诞生了不少精彩瞬间&#xff0c;在小组赛中&#xff0c;日本、韩国、沙特接连打败西班牙、葡萄牙、阿根廷等传统强队&#xff0c;摩洛哥也代表非洲球队首次挺进四强&#xf…

v8垃圾回收

文章目录内存的生命周期v8垃圾回收算法新生代Scavenge图例老生代Mark-SweepMark-Compact图例v8垃圾回收的弊端v8垃圾回收优化内存的生命周期 内存的生命周期可以分为三个阶段&#xff1a; 内存分配&#xff1a;按需分配内存内存食用&#xff1a;读写已经分配的内存内存释放&a…

Linux 管理联网 配置网络的四种方法 配置临时连接( ip 命令)

配置网络 # 网络接口是指网络中的计算机或网络设备与其他设备实现通讯的进出口。这里,主要是 指计算机的网络接口即 网卡设备 # 网络接口 -- 网卡 的命名 &#xff1a; 从RHEL7开始引入了一种新的“一致网络设备命名”的方式为网络接口命名,该…

【Java笔记】 深入理解序列化和反序列化

深入理解序列化和反序列化 文章目录深入理解序列化和反序列化1.是什么2.为什么3.怎么做3.1 实现Serializable接口3.2 实现Externalizable接口3.3 注意知识点3.4 serialVersionUID的作用4 扩展1.是什么 序列化&#xff1a;就是讲对象转化成字节序列的过程。 反序列化&#xff…

C++开发,这些GUI库一定不要错过

程序员宝藏库&#xff1a;https://gitee.com/sharetech_lee/CS-Books-Store 如果问Python这类集成度非常高的编程语言GUI开发用什么库&#xff0c;可以列举出很多不错的第三方库。 但是&#xff0c;如果这个问题放在C这种基础的编程语言上&#xff0c;很多同学估计一时间都无从…

用Gurobi+python求解设施选址问题(facility location)

参考&#xff1a;Gurobi 官方资源 设施选址&#xff08;Facility Location&#xff09; 1.背景介绍 设施选址问题在许多工业领域如物流&#xff0c;通信等都有应用&#xff0c;在本案例中展示如何解决设施选址问题&#xff0c;决策出仓库的数量和地点&#xff0c;为一些超市…

Crash Consistency on File Systems: 文件系统一致性保证 (1) Journaling File System

文件系统是操作系统中管理用户数据的重要模块。其中一项重要的任务就是确保用户数据的在系统突然崩溃之后&#xff0c;系统能够恢复出完整、一致的用户数据。本文将会分析两种流行的文件系统&#xff0c;Journaling File System 和 Log-structured File System是如何确保数据的…

dataFactory连接mysql详细配置教程

场景&#xff1a;最近项目提出机构用户中其中一个部门下用户人数有20万&#xff0c;加载的时候十分缓慢&#xff0c;本地想重现的一下&#xff0c;这就需要在本地表中生成>20万的数据&#xff0c;搜索了网上的教程写的都是很粗略。 目录 dataFactory连接mysql配置 安装包下…

第二证券|“20cm”涨停!盘中暴涨110%,又有港股暴力拉升

A股商场今日上午窄幅动摇&#xff0c;电子等板块领涨。北向资金半响净买入额到达26.10亿元。 港股商场今日上午动摇也较为温和。不过&#xff0c;仍有个股剧烈动摇。比如浦江世界上午暴升&#xff0c;盘中涨幅一度超过110%。 A股窄幅动摇 电子板块领涨 今日上午A股商场全体体…