• redis的实战项目03_set点赞、Zset点赞排序


    在学习redis中的基本数据类型时,只知道增删改查,并体会不到数据类型的优点和缺点。
    今天我们通过点赞和点赞排序,体验set和SortedSet的优点。
    list:底层是链表,查找时需要根据链表遍历查找,所以o(n)
    set:底层是哈希,所以查找速度非常快。
    在这里插入图片描述

    一、点赞

    1、需求:

    根据博客的id,用户为该博客进行点赞操作。
    需求:

    1. 同一个用户只能点赞一次,再次点赞则取消点赞
    2. 如果当前用户已经点赞,那么点赞按钮高亮显示(前端实现高亮,判断字段Blog类的isLike属性 )

    2、实现步骤描述:

    • 实现方法1 :直接使用数据库,不加redis缓存
      在数据库中建立一张表格,记录Blog的id,以及用户的id,那么点赞以后,在该表中进行记录一次。
      当再次点赞时,去数据库中判断是否存在。
      缺点:数据库性能不是很好,点赞判断比较多,对数据库中的压力比较大。

    • 实现方法2:用redis做缓存
      其实判断用户没有点赞过,其实是记录当前博客被哪些用户点赞过。
      key是 博客idvalue是点赞过的用户id,value记录哪些用户为该博客id点过赞。
      也就是需要一个集合,所有点过赞的id,全部放进去。
      当需要再次点赞时,判断该用户id,在集合中是否存在。
      一个用户只能点赞一次。那么就是说该集合中用户id不能重复。
      对redis的数据类型要求:set集合能满足以下要求:
      1.必须是一个集合
      2.必须满足元素唯一性。


    当用户点击 点赞按钮业务流程:

    1. 获取用户的信息
    2. 判断该用户是否已经点过赞了。也就是说用户在redis中是否存在
    3. 存在,说明已经点过赞了
      1. 在数据库中文字id对应的Like字段-1 【点赞数量减一】
      2. 从reids中删除用户的信息 【说明该用户取消点赞了】
    4. 不存在,说明还没有点赞
      1. 在数据库中文字id对应的Like字段 +1
      2. 在redis中增加用户的信息 【说明该用户已经点赞了】

    3、代码业务:

    1. 给Blog类中添加一个isLike字段,表示是否被当前用户点赞
        /**
         * 是否点赞过了
         * @TableField(exist = false) :表示数据库中并没有该字段
         */
        @TableField(exist = false)
        private Boolean isLike;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 修改点赞功能,利用Redis的Set集合判断是否点赞过【is member有无该用户id】,未点赞过则点赞数+1【添加用户id】,已点赞过的则点赞数-1
      @Resource
        private IUserService userService;
        @Resource
        private StringRedisTemplate stringRedisTemplate;
        /**
         * 为该文章id,文章点赞功能
         * @param id :文章id
         * @return
         */
        @Override
        public Result likeBlog(Long id) {
            // 1. 获取用户的信息
            UserDTO user = UserHolder.getUser();
            Long userId = user.getId();
            // 2. 判断该用户在redis中是否存在。是否已经点赞了
            String key = "bolg:liked:" + id;
            Boolean member = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
            if (BooleanUtil.isTrue(member)){
                // 3. 存在说明,已经点赞了
                // 3.1 从数据库中的like字段中-1
                boolean isSuccess = update().setSql("liked = liked-1").eq("id", id).update();
                // 3.2 从redis中删除用户信息
                if (isSuccess) {
                    stringRedisTemplate.opsForSet().remove(key,userId.toString());
                }
            }else{
                // 4. 不存在,说明还没有点赞
                // 4.1 在数据库中的Like字段中+1
                boolean isSuccess = update().setSql("liked = liked+1").eq("id", id).update();
                // 4.2 在redis中增加用户信息
                if (isSuccess) {
                    stringRedisTemplate.opsForSet().add(key, userId.toString());
                }
            }
            return Result.ok();
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    1. 修改根据id查询Blog的业务,判断当前登录用户是否点赞过,赋值给isLike字段
     /**
         * 根据id,获取博客
         * @param id
         * @return
         */
        @Override
        public Result queryBlogById(int id) {
            // 1. 根据博客id,获取博客
            Blog blog = getById(id);
            if (blog == null){
                return Result.fail("博客不存在");
            }
            // 2. 查询blog有关的用户信息
            queryBlogUser(blog);
            // 3. 查看当前用户是否点赞
            isBlogLiked(blog);
            return Result.ok(blog);
        }
        
        private void isBlogLiked(Blog blog) {
            // 1. 获取当前用户信息
            UserDTO user = UserHolder.getUser();
            Long userid = user.getId();
            // 根据当前用户去redis查询是否已经点赞了
            String key = "bolg:liked:" + blog.getId();
            Boolean member = stringRedisTemplate.opsForSet().isMember(key, userid.toString());
            blog.setIsLike(BooleanUtil.isTrue(member));
        }
    
        /**
         * 根据id,获取用户信息
         * @param blog
         */
        private void queryBlogUser(Blog blog) {
            Long userId = blog.getUserId();
            User user = userService.getById(userId);
            blog.setName(user.getNickName());
            blog.setIcon(user.getIcon());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    1. 修改分页查询Blog业务,判断当前登录的用户是否点赞过,赋值给isLike字段。
        /**
         * 分页查询热门文章
         * @param current
         * @return
         */
        @Override
        public Result queryHotBlog(Integer current) {
            // 根据用户查询
            Page<Blog> page = query()
                    .orderByDesc("liked")
                    .page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
            // 获取当前页数据
            List<Blog> records = page.getRecords();
            // 查询用户
            records.forEach(blog -> {
                this.queryBlogUser(blog);
                this.isBlogLiked(blog);
            });
    //        records.forEach(this::queryBlogUser);
    
            return Result.ok(records);
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    二、点赞排行榜

    1、需求:

    1. 把为该文章点过赞的人显示出来,比如只显示出前五个人
    2. 顺序是:根据时间排序,最先点赞的靠前
      在这里插入图片描述

    之前是单独完成点赞业务时,用的是redis中的set集合,因为set可以满足两点:
    第一是value元素不唯一性,因为一个用户只允许为一个博客点赞一次。
    第二是是一个集合,这样存多个元素。

    而如今要实现的是点赞排行榜,那么set不支持排序,那么继续使用set作为点赞就不适合了,那么可以从集合中进行挑选:

    • list:是集合、支持排序、不支持唯一性、根据底层链表查找比较慢
    • set:是集合、不支持排序、支持唯一性、底层是hash表,查找速度非常快
    • SortedSet:是集合、支持排序、支持唯一性。

    在这里插入图片描述
    SortedSet:
    添加元素是:ZADD
    判断是否存在:Zscore:根据指定元素,获取对应的分数。
    查询范围:zrange:从索引开始0到n

    127.0.0.1:6379[3]> ZADD key01 888 huyelin 999 zhangsan  123 lisi 
    (integer) 3
    127.0.0.1:6379[3]> ZSCORE key01 huyelin
    "888"
    127.0.0.1:6379[3]> ZSCORE key01 zhangsan
    "999"  //查到的话返回的是元素的值
    127.0.0.1:6379[3]> ZSCORE key01 jfklsajdf;lkj
    (nil)  // 返回的是空
    127.0.0.1:6379[3]> ZRANGE key01 0 2
    lisi
    huyelin
    zhangsan
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2、实现步骤描述:

    1. 从redis中获取,为该文章点赞的所有用户id
    2. 返回的是set,需要解析成list列表
    3. 根据用户id,查询出所有的信息,然后提出部分信息,交给实体类UserDTO传给前端

    3、代码业务:

        /**
         * 获取点赞列表
         * @param id :文章id
         * @return
         */
        @Override
        public Result queryLikes(int id) {
            // 1. 查询都有哪些用户点赞了。查询出前五条数据:
            String key = "bolg:liked:" +id;
            Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);
            //2.解析出其中的用户id
            List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());
            // 3. 根据用户的id,查询所有的用户信息,放入准备好的实体类【UserDTO】返回前端
            String idstr = StrUtil.join(",",ids); // 将ids字符串,进行分割
            System.out.println("查看字符串进行分割-------》"+idstr);
            List<User> userList = userService.query().in("id", ids).last("ORDER BY FIELD(id," + idstr + ")").list();
            List<UserDTO> userDTOS = userList.stream()
                    .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
                    .collect(Collectors.toList());
    
            return Result.ok(userDTOS);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    其中准备好的实体类,返回前端的数据是:

    @Data
    public class UserDTO {
    	// 用户id
        private Long id;
        // 名字
        private String nickName;
        //头像
        private String icon;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    三、所有的业务层代码:用SortedSet进行实现点赞、排序点赞

    package com.hmdp.service.impl;
    
    import cn.hutool.core.bean.BeanUtil;
    import cn.hutool.core.util.BooleanUtil;
    import cn.hutool.core.util.StrUtil;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.hmdp.dto.Result;
    import com.hmdp.dto.UserDTO;
    import com.hmdp.entity.Blog;
    import com.hmdp.entity.User;
    import com.hmdp.mapper.BlogMapper;
    import com.hmdp.service.IBlogService;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.hmdp.service.IUserService;
    import com.hmdp.utils.SystemConstants;
    import com.hmdp.utils.UserHolder;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    import java.util.List;
    import java.util.Set;
    import java.util.stream.Collectors;
    
    /**
     * <p>
     *  服务实现类
     * </p>
     *
     */
    @Service
    public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {
        @Resource
        private IUserService userService;
        @Resource
        private StringRedisTemplate stringRedisTemplate;
        /**
         * 根据前端传过来的文字id,该用户为该文章点赞和取消赞的功能
         * @param id :文章id
         * @return
         */
        @Override
        public Result likeBlog(Long id) {
            // 1. 获取用户的信息
            UserDTO user = UserHolder.getUser();
            Long userId = user.getId();
            // 2. 判断该用户在redis中是否存在。是否已经点赞了
            String key = "bolg:liked:" + id;
    //        Boolean member = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
            // 根据key和value,得到的是分数
            Double member = stringRedisTemplate.opsForZSet().score(key,userId.toString());
            if (member != null ){
                // 3. 如果说分数不为空,说明有分数。说明 已经点赞了
                // 3.1 从数据库中的like字段中-1
                boolean isSuccess = update().setSql("liked = liked-1").eq("id", id).update();
                // 3.2 从redis中删除用户信息
                if (isSuccess) {
    //                stringRedisTemplate.opsForSet().remove(key,userId.toString());
                    // 删除该用户信息
                    stringRedisTemplate.opsForZSet().remove(key,userId.toString());
                }
            }else{
                // 4. 不存在,说明还没有点赞
                // 4.1 在数据库中的Like字段中+1
                boolean isSuccess = update().setSql("liked = liked+1").eq("id", id).update();
                // 4.2 在redis中增加用户信息
                if (isSuccess) {
                    stringRedisTemplate.opsForZSet().add(key,userId.toString(),System.currentTimeMillis());
                }
            }
            return Result.ok();
        }
    
        /**
         * 分页查询热门文章
         * @param current
         * @return
         */
        @Override
        public Result queryHotBlog(Integer current) {
            // 根据用户查询
            Page<Blog> page = query()
                    .orderByDesc("liked")
                    .page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
            // 获取当前页数据
            List<Blog> records = page.getRecords();
            // 查询用户
            records.forEach(blog -> {
                //根据id,获取用户信息
                this.queryBlogUser(blog);
                //  查看当前用户是否点赞
                this.isBlogLiked(blog);
            });
    //        records.forEach(this::queryBlogUser);
    
            return Result.ok(records);
        }
    
    
    
        /**
         * 根据博客id,获取博客
         * @param id
         * @return
         */
        @Override
        public Result queryBlogById(int id) {
            // 1. 根据博客id,获取博客
            Blog blog = getById(id);
            if (blog == null){
                return Result.fail("博客不存在");
            }
            // 2. 查询blog有关的用户信息
            queryBlogUser(blog);
            // 3. 查看当前用户是否点赞
            isBlogLiked(blog);
            return Result.ok(blog);
        }
    
        /**
         * 根据当前用户查询是否已经点赞
         * @param blog
         */
        private void isBlogLiked(Blog blog) {
            // 1. 获取当前用户信息
            UserDTO user = UserHolder.getUser();
            // 判断,如果当前用户没有登录,不需要再去查是否点赞,直接返回空即可
            if (user ==null){
                return;
            }
            Long userid = user.getId();
    
            // 根据当前用户去redis查询是否已经点赞了
            String key = "bolg:liked:" + blog.getId();
    //        Boolean member = stringRedisTemplate.opsForSet().isMember(key, userid.toString());
            Double member = stringRedisTemplate.opsForZSet().score(key,userid.toString());
            if (member != null){
                blog.setIsLike(true);
            }else {
                blog.setIsLike(false);
            }
        }
    
    
        /**
         * 根据id,获取用户信息
         * @param blog
         */
        private void queryBlogUser(Blog blog) {
            Long userId = blog.getUserId();
            User user = userService.getById(userId);
            blog.setName(user.getNickName());
            blog.setIcon(user.getIcon());
        }
    
        /**
         * 获取点赞列表
         * @param id :文章id
         * @return
         */
        @Override
        public Result queryLikes(int id) {
            // 1. 查询都有哪些用户点赞了。查询出前五条数据:
            String key = "bolg:liked:" +id;
            Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);
            //2.解析出其中的用户id
            List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());
            // 3. 根据用户的id,查询所有的用户信息,放入准备好的实体类【UserDTO】返回前端
            String idstr = StrUtil.join(",",ids); // 将ids字符串,进行分割
            System.out.println("查看字符串进行分割-------》"+idstr);
            List<User> userList = userService.query().in("id", ids).last("ORDER BY FIELD(id," + idstr + ")").list();
            List<UserDTO> userDTOS = userList.stream()
                    .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
                    .collect(Collectors.toList());
    
            return Result.ok(userDTOS);
        }
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
  • 相关阅读:
    Prometheus采集Java程序指标信息
    springboot基于微信小程序的校园体育运动场地及器材租凭系统设计与实现毕业设计源码131052
    【Qt】开发环境搭建
    SpringBoot + uniApp实现的掌上生鲜超市购物微信小程序系统 附带详细运行指导视频
    CSRF漏洞
    VS正确引入头文件仍然报C2065等错(不同编码代码页导致)
    朱庚彪医师通过中国心理学会(CPS)临床心理师注册系统评审
    springboot系列(二十六):如何实现word模板单页导出?这你得会|超级详细,建议收藏
    QT事件说明
    生产部长修炼宝典②:企业如何利用大数据分析平台实现生产异常的快反和处置?
  • 原文地址:https://blog.csdn.net/weixin_43989347/article/details/124914215