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);
    }
相关推荐
极限实验室3 小时前
IK 字段级别词典的升级之路
数据库
曾几何时`4 小时前
MySQL(配置)——MariaDB使用
数据库·mysql
努力学习java的哈吉米大王4 小时前
MySQL——MVCC
数据库·mysql
数据要素X4 小时前
【数据架构10】数字政府架构篇
大数据·运维·数据库·人工智能·架构
lixzest5 小时前
Redis实现数据传输简介
数据库·redis·缓存
搬砖的小熊猫5 小时前
MySQL常见面试题
数据库·mysql
lang201509285 小时前
如何使用 Apache Ignite 作为 Spring 框架的缓存(Spring Cache)后端
spring·缓存·apache·ignite
Linux技术支持工程师5 小时前
二十八、【Linux系统域名解析】DNS安装、子域授权、缓存DNS、分离解析、多域名解析
linux·运维·服务器·缓存·centos
weixin_419658315 小时前
MySQL的JDBC编程
数据库·mysql
JavaLearnerZGQ5 小时前
Docker部署Nacos
数据库·docker·容器