07.利用Redis实现点赞排行榜功能

学习目标:

提示:学习如何利用Redisson实现点赞排行榜功能,按照时间顺序

当用户给某一篇文章点赞后,会再数据库中存储一条数据,并且在Redis中存储一条数据为当前博客的点赞用户标识,来区分哪个用户对文章进行了点赞,使用ZSet数据结构对点赞用户进行排序来实现排行榜功能


学习产出:

解决方案:

  1. 点赞后的用户记录在Redis的set数据类型中

1. 准备pom环境

java 复制代码
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>
        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.17</version>
        </dependency>
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.23.1</version>
        </dependency>

2. 配置ThreadLocal和过滤器

java 复制代码
public class UserHolder {
    private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();

    public static void saveUser(UserDTO user){
        tl.set(user);
    }

    public static UserDTO getUser(){
        return tl.get();
    }

    public static void removeUser(){
        tl.remove();
    }
}
java 复制代码
@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Autowired
    private StringRedisTemplate redis;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/user/code","/user/login","/blog/hot","/shop/**","/shop-type/**","/voucher/**").order(2);
        registry.addInterceptor(new RefreshTokenInterceptor(redis)).addPathPatterns("/**").order(1);
    }
}
---------------------------------------------
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {

    //controller执行之前
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1.判断是否需要拦截ThreadLocal
        if (UserHolder.getUser()==null) {
            response.setStatus(401);
            return false;
        }
        //7.放行
        return true;
    }
    //渲染后返回给前台数据前
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //移除用户,避免内存泄露
        UserHolder.removeUser();
    }
}
---------------------------------------------------
@Slf4j
public class RefreshTokenInterceptor implements HandlerInterceptor {
    //这个对象不是由spring管理的所以不能用注解自动注入

    private StringRedisTemplate redis;

    public RefreshTokenInterceptor(StringRedisTemplate redis) {
        this.redis = redis;
    }

    //controller执行之前
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //1.获取请求头中的token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            return true;
        }
        //2.基于token获取redis中的用户
        //通过key取到hash中的map集合数据
        Map<Object, Object> userMap = redis.opsForHash().entries("login:token:" + token);
        //3.判断用户是否存在
        if (userMap.isEmpty()) {
            return true;
        }
        //5.将查询到的hash数据转为userDto对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        //6.存在,保存用户信息到ThreadLocal中
        UserHolder.saveUser(userDTO);
        //7.刷新token有效期
        redis.expire(LOGIN_USER_KEY + token, 30, TimeUnit.MINUTES);
        log.info("我是第一个拦截器当前拦截所有请求的用户为,线程为{},{}",UserHolder.getUser(),Thread.currentThread());
        //8.放行
        return true;
    }

3. Controller层:负责接收请求和向下分配

java 复制代码
@RestController
@RequestMapping("/blog")
public class BlogController{
    @Resource
    private IBlogService blogService;
    @PutMapping("/like/{id}")
    public Result likeBlog(@PathVariable("id") Long id) {

        return blogService.likeBlog(id);
    }
}

4. Service层:负责业务的处理逻辑点赞功能,将文章的点赞用户以时间戳为分数存入Redis

java 复制代码
@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {
    @Autowired
    private IUserService userService;
    @Resource
    private StringRedisTemplate redis;
    @Override
    public Result likeBlog(Long id) {
        //1.获取登录用户
        Long userId = UserHolder.getUser().getId();
        //2.判断当前用户是否已经点赞
        String key = "blog:liked:" + id;
        //获取当前登录用户的分数,若文章中的用户id分数为null说明未点赞
        Double score = redis.opsForZSet().score(key, userId.toString());
        if (score == null) {
            //3.如果未点赞,可以点赞
            //3.1 点赞+1
            boolean isSuccess = update().setSql("liked= liked +1").eq("id", id).update();
            //3.2保存当前点赞用户到Redis的文章set集合中,文章set集合中记录的是点赞用户的id,
            //分数是时间戳,可以进行排序
            if (isSuccess) {
            	//存入Redis的分数值以当前时间戳存入
                redis.opsForZSet().add(key, userId.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) {
                redis.opsForZSet().remove(key, userId.toString());
            }
        }
        return null;
    }
}

5. 上述为下面的排行榜做铺垫

查询当前文章的点赞排行榜,id是文章id号

java 复制代码
	@PutMapping("/likes/{id}")
    public Result likesBlog(@PathVariable("id") Long id) {
        return blogService.queryBlogLikes(id);
    }
java 复制代码
@Override
    public Result queryBlogLikes(Long id) {
        String key="blog:liked:" + id;
        //取出前五条数据
        Set<String> rangeData = redis.opsForZSet().range(key, 0, 4);
        if (rangeData==null) {
            return Result.ok(Collections.emptyList());
        }
        //将文章点赞的前五条用户id转换为Long类型
        List<Long> ids = rangeData.stream().map(Long::valueOf).collect(Collectors.toList());
        String idStr = StrUtil.join(",", ids);
        //去数据库把这些用户查询出来,并且数据脱敏返回给前端
        List<User> users = userService.query().in("id",ids).last("order by field(id,"+idStr+")").list();
        UserDTO userData = BeanUtil.copyProperties(users, UserDTO.class);
        return Result.ok(userData);
    }
相关推荐
Hello.Reader37 分钟前
Redis大Key问题全解析
数据库·redis·bootstrap
靖顺3 小时前
【OceanBase 诊断调优】—— packet fly cost too much time 的根因分析
数据库·oceanbase
liuxin334455663 小时前
学籍管理系统:实现教育管理现代化
java·开发语言·前端·数据库·安全
B1nna4 小时前
Redis学习(三)缓存
redis·学习·缓存
yuanbenshidiaos5 小时前
C++--------------树
java·数据库·c++
程序员JerrySUN6 小时前
Yocto 项目 - 共享状态缓存 (Shared State Cache) 机制
linux·嵌入式硬件·物联网·缓存·系统架构
dengjiayue6 小时前
MySQL 查询大偏移量(LIMIT)问题分析
数据库·mysql
言之。6 小时前
【MySQL】在MySQL中如何定位慢查询?
数据库·mysql
DashVector7 小时前
如何通过HTTP API插入Doc
数据库·人工智能·http·阿里云·向量检索
DashVector7 小时前
如何通过HTTP API分组检索Doc
服务器·数据库·http·数据库开发·数据库架构