Redis实现笔记点赞排行榜 | 黑马点评-达人探店

news2025/5/11 2:53:08

一、发布探店笔记

探店笔记类似点评网站的评价,往往是图文结合。对应的表有两个

  • 探店笔记表(主键、商户id、用户id、标题、文字、图片、探店文字描述、点赞数量、评论数量)
  • 评价表(笔记的评价)

 先上传图片请求一次保存图片接口,再点发布请求发布接口。这两个接口已经写好

二、实现查看笔记接口

BlogController

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

    @Resource
    private IBlogService blogService;
   
    @GetMapping("/hot")
    public Result queryHotBlog(@RequestParam(value = "current", defaultValue = "1") Integer current) {

        return blogService.queryHotBlog(current);
    }

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

IBlogService

public interface IBlogService extends IService<Blog> {

    Result queryBlogById(String id);

    Result queryHotBlog(Integer current);
}

BlogServiceImpl

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

    @Autowired
    private IUserService userService;

    @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(this::queryBlogUser);
        return Result.ok(records);
    }

    private void queryBlogUser(Blog blog) {
        Long userId = blog.getUserId();
        User user = userService.getById(userId);
        blog.setName(user.getNickName());
        blog.setIcon(user.getIcon());
    }

    @Override
    public Result queryBlogById(String id) {
        Blog blog = getById(id);
        
        if(blog == null){
            return Result.fail("笔记不存在!");
        }

        queryBlogUser(blog);

        return Result.ok(blog);
    }
}

三、点赞功能

现在已经写好的点赞接口

问题:这样写接口,可以一直按点赞重复点赞

需求

  • 同一个用户只能点赞一次,再次点赞则取消点赞
  • 如果当前用户已经点赞,则点赞按钮高亮显(前端已经实现,判断blog类的isLike属性)

实现步骤

  1. 给Blog类中添加一个isLike字段,表示是否被当前用户点赞
  2. 修改点赞功能,利用redis的set集合判断用户是否赞过,未赞则点赞数+1,赞过则-1
  3. 修改根据id查询blog的业务,判断当前用户是否点赞过,赋值给isLike字段
  4. 修改分页查询blog业务,判断当前用户是否赞过,赋值isLike字段

业务实现

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

    @Resource
    private IBlogService blogService;

    @PutMapping("/like/{id}")
    public Result likeBlog(@PathVariable("id") Long id) {
        return blogService.likeBlog(id);
    }

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

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

编写完点赞操作的接口后还要修改之前的查询接口,增加查询是否已经点赞 

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

    @Autowired
    private IUserService userService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @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);
        });
        return Result.ok(records);
    }

    @Override
    public Result likeBlog(Long id) {
        // 1、获取登录用户
        UserDTO user = UserHolder.getUser();
        // 2、判断当前登录用户是否已经点赞
        Boolean isMember = stringRedisTemplate.opsForSet().isMember(RedisConstants.BLOG_LIKED_KEY + id, user.getId().toString());
        if(BooleanUtil.isFalse(isMember)) {
            // 3、如果未点赞,可以点赞
            // 3.1、数据库点赞数 +1
            boolean isSuccess = update().setSql("liked = liked+1").eq("id", id).update();
            // 3.2、保存用户到 Redis 的 set 集合
            if(isSuccess){
                stringRedisTemplate.opsForSet().add(RedisConstants.BLOG_LIKED_KEY + id, user.getId().toString());
            }
        } else {
            // 4、如果已点赞,取消点赞
            // 4.1、数据库点赞数 -1
            boolean isSuccess = update().setSql("liked = liked - 1").eq("id", id).update();
            // 4.2、把用户从 Redis 的 set 集合移除
            if(isSuccess){
                stringRedisTemplate.opsForSet().remove(RedisConstants.BLOG_LIKED_KEY + id, user.getId().toString());
            }
        }
        return Result.ok();
    }


    private void queryBlogUser(Blog blog) {
        Long userId = blog.getUserId();
        User user = userService.getById(userId);
        blog.setName(user.getNickName());
        blog.setIcon(user.getIcon());
    }

    @Override
    public Result queryBlogById(String id) {
        Blog blog = getById(id);
        
        if(blog == null){
            return Result.fail("笔记不存在!");
        }

        queryBlogUser(blog);
        // 查询 Blog 是否被点赞
        isBlogLiked(blog);

        return Result.ok(blog);
    }

    private void isBlogLiked(Blog blog) {
        Long userId = blog.getUserId();
        String key = RedisConstants.BLOG_LIKED_KEY + blog.getId();
        Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
        blog.setIsLike(BooleanUtil.isTrue(isMember));
    }
}

四、点赞排行榜 

查出给这个笔记点赞的人,类似微信朋友圈的点赞,可以展示谁点赞了,而且我们要进行排序

所以我们得用SortedSet这种数据类型

1、修改点赞逻辑

把原本存入set改为存入zset多加个分数,分数就是时间戳

@Override
    public Result likeBlog(Long id) {
        // 1、获取登录用户
        UserDTO user = UserHolder.getUser();
        // 2、判断当前登录用户是否已经点赞
        Double score = stringRedisTemplate.opsForZSet().score(RedisConstants.BLOG_LIKED_KEY + id, user.getId().toString());
        if(score == null) {
            // 3、如果未点赞,可以点赞
            // 3.1、数据库点赞数 +1
            boolean isSuccess = update().setSql("liked = liked+1").eq("id", id).update();
            // 3.2、保存用户到 Redis 的 set 集合
            if(isSuccess){
                // 时间作为 key 的 score
                stringRedisTemplate.opsForZSet().add(RedisConstants.BLOG_LIKED_KEY + id, user.getId().toString(), System.currentTimeMillis());
            }
        } else {
            // 4、如果已点赞,取消点赞
            // 4.1、数据库点赞数 -1
            boolean isSuccess = update().setSql("liked = liked - 1").eq("id", id).update();
            // 4.2、把用户从 Redis 的 set 集合移除
            if(isSuccess){
                stringRedisTemplate.opsForZSet().remove(RedisConstants.BLOG_LIKED_KEY + id, user.getId().toString());
            }
        }
        return Result.ok();
    }

然后是否被点赞的方法也要修改,根据key取出分数,分数不为null就是点赞过了

 private void isBlogLiked(Blog blog) {
        UserDTO user = UserHolder.getUser();
        if(user == null){
            return;
        }
        Long userId = user.getId();
        String key = RedisConstants.BLOG_LIKED_KEY + blog.getId();
        Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
        blog.setIsLike(score != null);
    }

2、点赞排行榜功能

需求:实现前五个点赞的用户返回

我们先用动态id去redis中查询出前五个点赞用户的id

然后根据id去数据库中查询信息封装到dto再返回

    @GetMapping("/likes/{id}")
    public Result queryBlogLikes(@PathVariable("id") String id) {
        return blogService.queryBlogLikes(id);
    }
    @Override
    public Result queryBlogLikes(String id) {
        String key = RedisConstants.BLOG_LIKED_KEY + id;
        // 查询 top5 的点赞用户
        Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);
        if(top5 == null){
            return Result.ok(Collections.emptyList());
        }
        // 解析出其中的用户id
        List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());
        String join = StrUtil.join(",", ids);
        // 根据用户id查询用户
        List<UserDTO> userDTOS = userService.query().in("id", ids).last("order by filed(id, "+join+")").list()
                .stream()
                .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
                .collect(Collectors.toList());

        return Result.ok(userDTOS);
    }

注意:如果我们mp直接用in来查询根本不能保证点赞的顺序,因为in查询出来的是按照id顺序返回的,没有排序,我们要按照查询id的顺序来查,order by field(id,5,1)这样

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

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

相关文章

【Java基础】--Java排序

【Java基础】--Java排序1、选择排序(Select Sort)2、冒泡排序(Bubble Sort)3、插入排序(Insert Sort)4、希尔排序(Shell Sort)5、归并排序(Merge Sort)6、快速排序(Quick Sort)7、堆排序(Heap Sort)小结待排序的元素需要实现 Java 的 Comparable 接口&#xff0c;该接口有 com…

Facebook SEO中参与度的重要性

参与度&#xff0c;也就是大家所说的浏览量&#xff0c;在 Facebook SEO中也叫做页面访问者参与度。一般来说&#xff0c; Facebook的用户在上面停留的时间越长代表着它在用户心目中的形象越好&#xff0c;这也是为什么 Facebook上的访客愿意打开自己的 Facebook页面让好友帮忙…

使用vite构建vue3项目详细介绍(ts+pinia+sass+vue-router+axios+element-plus)

使用vite构建vue3项目详细介绍(tspiniasassvue-routeraxioselement-plus) 1. 创建项目 npm init vitelatest 2. 配置 vite.config.ts path需要安装--npm install types/node --save-dev import vue from vitejs/plugin-vue; import { resolve } from path; import { defineC…

Jupyter的安装与默认目录的切换

下载与安装 清华大学开源软件镜像站 使用国内镜像下载更快&#xff0c;官网下载很慢 下载msi镜像文件&#xff0c;打开安装&#xff1a; 安装完成后得到4个文件 Reset Spyder...和Anaconda Powershell....都是相应的配置&#xff0c;其中后者是Jupyter和anaconda的dos命令窗口…

Coolify系列02-从0到1超详细手把手教你上手Coolify

重启 如果由于某种原因&#xff0c;你的实例崩溃了&#xff0c;你可以用下面的命令重新启动它: wget -q https://get.coollabs.io/coolify/install.sh \ -O install.sh; sudo bash ./install.sh -r防火墙设置 您需要在防火墙中允许以下端口 Coolify: 3000 (required)Revers…

【Mysql】Mysql的存储引擎

【Mysql】Mysql的存储引擎 文章目录【Mysql】Mysql的存储引擎1.概述2. 特点2.1 InnoDB2.2 MyISAM2.3 Memory2.4 区别3. 选择1.概述 **存储引擎&#xff1a;存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式。**存储引擎是基于表的&#xff0c;而不是 基于库的&…

HTTPS工作原理详解加密(TLS握手)过程

HTTPS概念 HTTPS就是一个有安全保障的HTTP通信&#xff0c;我们都知道&#xff0c;http是明文传输的&#xff0c;http报文是人肉眼就可识别的ASCII码&#xff0c;在通信过程中&#xff0c;http报文很容易被黑客窃听、篡改、伪造&#xff0c;而在互联网交易中&#xff0c;我们必…

【1】初识Linux

学习笔记目录 学习教程&#xff1a;B站 “黑马程序员” 初识Linux--入门Linux基础命令--会用Linux权限管控--懂权限Linux实用操作--熟练实战软件部署--深入掌握脚本&自动化--用的更强项目实战--学到经验云平台技术--紧跟潮流 操作系统概述 1.计算机由硬件和软件两个主要…

AcWing 12. 背包问题求具体方案

AcWing 12. 背包问题求具体方案AcWing 12. 背包问题求具体方案&#xff08;1&#xff09;问题&#xff08;2&#xff09;分析&#xff08;3&#xff09;代码AcWing 12. 背包问题求具体方案 &#xff08;1&#xff09;问题 &#xff08;2&#xff09;分析 我们先看一下这道题中…

tomcat更改默认端口

如下图把conf目录下的server.xml的下图所示处由原来的8080改为需要的即可&#xff1b;当前改为8087&#xff1b;保存之后重启tomcat&#xff1b; 网上一个资料说如果要运行2个tomcat&#xff0c;把下图的8009改为自己的&#xff0c;例如18009&#xff0c; 把下图的8005改为自己…

pythpon基础:创建文件索引升级版

需求环境 这是公司的一个需求&#xff0c;有一份很庞大的数据由好多视频文件组成&#xff0c;总共有12T左右&#xff0c;视频来源是一些下载的视频素材。每隔一段时间就要将一部分筛选好的视频文件剪切到server02服务器进行转码&#xff0c;筛选没被选中的文件将被删除。从下载…

5. 蒙特卡洛方法

蒙特卡洛方法5. 离轨策略5.1 策略评估&#xff08;基于重要度采样&#xff09;5.1.1 计算目标策略下的状态值5.1.2 蒙特卡洛算法&#xff08;状态值估计&#xff09;5.1.3 增量式的实现5.2 策略迭代5.3 减少重要性采样方差的方法5.3.1 折扣敏感的重要性采样5.3.2 每决策重要性抽…

go 语言 string 类型思考

string 作为 go 语言中的基础类型&#xff0c;其实有一些需要反复揣摩的&#xff0c;可能是我们使用的场景太简单&#xff0c;也可能是我们不需要那可怜的一点优化来提高性能&#xff0c;对它也就没那么上心了。 文章运行环境&#xff1a;go version go1.16.6 darwin/amd64 并…

浅析Java中的final关键字

一.final关键字的基本用法 在Java中&#xff0c;final关键字可以用来修饰类、方法和变量&#xff08;包括成员变量和局部变量&#xff09;。下面就从这三个方面来了解一下final关键字的基本用法。 1.修饰类 当用final修饰一个类时&#xff0c;表明这个类不能被继承。也就是说&a…

ACL访问控制的基本实例

典型案例&#xff1a; 配置需求∶ 在Router上部署基本ACL后&#xff0c;ACL将试图穿越Router的源地址为192.168.1.0/24网段的数据包过滤掉&#xff0c;并放行其他流量&#xff0c;从而禁止192.168.1.0/24网段的用户访问Router右侧的服务器网络。 配置&#xff1a; 1、Router已…

第三章 变量

一、数据类型&#xff08;P40&#xff09; 每一种数据都定义了明确的数据类型&#xff0c;在内存中分配了不同大小的内存空间(字节)。二、整数类型 整型的使用细节&#xff1a; &#xff08;1&#xff09;Java 各整数类型有固定的范围和字段长度&#xff0c;不受具体OS【操作系…

Java多线程(四)——ThreadPoolExecutor源码解析

ThreadPoolExecutor源码解析 多线程场景下&#xff0c;手动创建线程有许多缺点&#xff1a; 频繁创建、销毁线程会消耗大量 CPU 资源&#xff0c;销毁线程后需要被回收&#xff0c;对 GC 垃圾回收也有一定的压力 使用线程池有许多好处&#xff1a; 降低 CPU 资源消耗。通过…

Linux运维之解决服务器挖矿木马问题

文章目录1 挖矿木马1.1 定义1.2 挖矿特征1.3 解决挖矿木马1.3.1 阻断异常网络通信&#xff08;非必需&#xff09;1.3.2 清除定时任务1.3.3 清除启动项1.3.4 清除SSH公钥1.3.5 清除木马进程1.4 其他常见问题1.4.1 清除木马后又100%1.4.2 CPU占用100%却看不到进程1 挖矿木马 1.…

Python OS 文件目录方法 os.walk()

Python OS 文件/目录方法 os.walk() 概述 os.walk() 方法用于通过在目录树中游走输出在目录中的文件名&#xff0c;向上或者向下。 os.walk() 方法是一个简单易用的文件、目录遍历器&#xff0c;可以帮助我们高效的处理文件、目录方面的事情。 在Unix&#xff0c;Windows中…

BFS(三)腐烂的橘子(感染问题)

994. 腐烂的橘子 在给定的 m x n 网格 grid 中&#xff0c;每个单元格可以有以下三个值之一&#xff1a; 值 0 代表空单元格&#xff1b; 值 1 代表新鲜橘子&#xff1b; 值 2 代表腐烂的橘子。 每分钟&#xff0c;腐烂的橘子 周围 4 个方向上相邻 的…