SpringBoot+Redission实现排行榜功能的示例代码

 更新时间:2024年05月08日 10:03:38   作者:BaisuX  
这篇文章主要介绍了基于SpringBoot+Redission实现排行榜功能,实现一个排行榜,要求按照分数和达成这个分数的时间排序,即相同分数下,时间早的在上面,文中通过代码示例介绍的非常详细,需要的朋友可以参考下

SpringBoot+Redission实现排行榜功能

demo地址:ranking-demo: 排行榜DEMO (gitee.com)

一、业务需求

实现一个排行榜,要求按照分数和达成这个分数的时间排序,即相同分数下,时间早的在上面

二、Redis中的zSet(有序集合)

1.简介

Redis 的 zSet(也称为有序集合)是一种特殊的数据结构,它同时包含了集合和有序列表的特性。在 zSet 中,每个成员都有一个分数(score)与之关联,这个分数可以是浮点数,用于对集合中的元素进行排序。

2.特点

  • 元素唯一:就像集合一样,zSet 中不允许有重复成员。
  • 有序性:集合中的元素按照其关联的分数值进行升序排序。
  • 操作丰富:支持添加、删除成员,获取指定范围的成员,根据分数查询成员,计算交集、并集、差集等操作。

3.常用命令

  • ZADD:向有序集合中添加一个或多个成员,或者更新已存在成员的分数。
  • ZRANGE:返回有序集合中指定区间内的成员,通过索引位置来获取,从0开始。
  • ZRANGEBYSCORE:返回有序集合中指定分数区间的成员。
  • ZCARD:获取有序集合的成员数量。
  • ZREM:移除有序集合中的一个或多个成员。
  • ZREVRANGE:类似于 ZRANGE,但返回的是从高分到低分的成员。
  • ZINCRBY:为有序集合中的成员的分数加上给定值。
  • ZCOUNT:计算有序集合中指定分数区间的成员数量。
  • ZRANK/ZREVRANK:获取成员在有序集合中的排名,ZRANK 是从低分到高分,ZREVRANK 是从高分到低分。

4.测试

> ZADD zsetkey 1 member1
1
> ZADD zsetkey 1 member2
1
> ZADD zsetkey 1 member9
1
> ZADD zsetkey 1 member5
1
> ZREVRANGE zsetkey 0 10 WITHSCORES
member9
1
member5
1
member2
1
member1
1

5.总结

zSet可以很好的实现分数排序,但是在相同的分数下,会按照成员的名称进行排序,所以要在此基础上增加时间

三、增加时间数据

为了增加完成时间,我们可以引进一个倒计时的概念,假设一共9秒

用户A在获得1分的时候在第2秒,那可以在Redis中存储1*10+(9-2) => 18

用户B在获得1分的时候在第6秒,那可以在Redis中存储1*10+(9-6) => 13

这样我们在获取分数的时候,可以倒推出分数和完成时间

四、SpringBoot代码

1.引入Redis和Redission依赖

<!-- redis -->  
<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-data-redis</artifactId>  
</dependency>  
  
<!-- redisson -->  
<dependency>  
    <groupId>org.redisson</groupId>  
    <artifactId>redisson-spring-boot-starter</artifactId>  
    <version>3.20.1</version>  
</dependency>

2.application.yml配置

--- # redis配置  
spring:  
  redis:  
    # 地址  
    host: localhost  
    # 端口,默认为6379  
    port: 6379  
    # 数据库索引  
    database: 0  
    # 密码(如没有密码请注释掉)  
    # password:    # 连接超时时间  
    timeout: 10s  
    # 是否开启ssl  
    ssl: false  
  
--- # redisson配置  
redisson:  
  # redis key前缀  
  keyPrefix: ${spring.application.name}  
  # 线程池数量  
  threads: 4  
  # Netty线程池数量  
  nettyThreads: 8  
  # 单节点配置  
  singleServerConfig:  
    # 客户端名称  
    clientName: ${spring.application.name}  
    # 最小空闲连接数  
    connectionMinimumIdleSize: 8  
    # 连接池大小  
    connectionPoolSize: 32  
    # 连接空闲超时,单位:毫秒  
    idleConnectionTimeout: 10000  
    # 命令等待超时,单位:毫秒  
    timeout: 3000  
    # 发布和订阅连接池大小  
    subscriptionConnectionPoolSize: 50

3.Java代码

Constant

/**  
 * @author Baisu  
 * @classname RankingConstant  
 * @description 排行榜常量数据  
 * @since 2024/5/6  
 */
public class RankingConstant {  
  
    public static final Long BASIC_QUANTITY = 10000000000000L;  
  
    public static final Long MAXIMUM_TIME_TIMIT = 29991231235959L;  
}

Controller

import cn.hutool.core.date.DateTime;  
import cn.hutool.core.date.DateUtil;  
import com.ranking.demo.common.R;  
import com.ranking.demo.common.constant.RankingConstant;  
import com.ranking.demo.demain.RankingVo;  
import com.ranking.demo.utils.RankingUtil;  
import com.ranking.demo.utils.RedisKey;  
import com.ranking.demo.utils.RedisUtil;  
import org.redisson.client.protocol.ScoredEntry;  
import org.springframework.beans.factory.annotation.Value;  
import org.springframework.web.bind.annotation.*;  
  
import java.util.ArrayList;  
import java.util.Collection;  
import java.util.List;  
  
/**  
 * @author Baisu  
 * @since 2024/4/28  
 */
@RestController  
public class DemoRankingController {  
  
    @Value("${spring.application.name}")  
    private String applicationName;  
  
    /**  
     * 项目启动测试方法  
     *  
     * @return applicationName  
     */    
    @GetMapping("")  
    public String demo() {  
        return applicationName;  
    }  
  
    /**  
     * 生成测试数据  
     *  
     * @return ok  
     */    
	@GetMapping("/generate_test_data")  
    public R<Object> generateTestData() {  
        RedisUtil.addScoreByMember(RedisKey.getRankingDemoKey(), 1L, "10001");  
        RedisUtil.addScoreByMember(RedisKey.getRankingDemoKey(), 2L, "10002");  
        RedisUtil.addScoreByMember(RedisKey.getRankingDemoKey(), 3L, "10003");  
        RedisUtil.addScoreByMember(RedisKey.getRankingDemoKey(), 4L, "10004");  
        RedisUtil.addScoreByMember(RedisKey.getRankingDemoKey(), 5L, "10005");  
        return R.ok();  
    }  
  
    /**  
     * 获取排行榜数据  
     *  
     * @param top 数量  
     * @return 排行榜数据  
     */  
    @GetMapping("/get_ranking")  
    public R<Object> getRanking(@RequestParam("top") Integer top) {  
        Collection<ScoredEntry<Object>> ranking = RedisUtil.getRanking(RedisKey.getRankingDemoKey(), 0, top - 1);  
        if (ranking.size() == 0) {  
            return R.fail("暂无排行榜数据");  
        }  
        List<RankingVo> list = new ArrayList<>();  
        for (ScoredEntry<Object> entry : ranking) {  
            RankingVo vo = new RankingVo();  
            vo.setMember(entry.getValue().toString());  
            vo.setScore(RankingUtil.getScore(entry.getScore()));  
            vo.setTime(RankingUtil.getTimeStr(entry.getScore()));  
            list.add(vo);  
        }  
        return R.ok(list);  
    }  
  
    /**  
     * 增加成员分数值  
     *  
     * @param member 成员  
     * @return 是否增加成功  
     */  
    @GetMapping("/add_score_by_member")  
    public R<Object> addScoreByMember(@RequestParam("member") String member) {  
        Double scoreByMember = RedisUtil.getScoreByMember(RedisKey.getRankingDemoKey(), member);  
        if (scoreByMember == null) {  
            scoreByMember = 0.0;  
        }  
        RedisUtil.addScoreByMember(RedisKey.getRankingDemoKey(), RankingUtil.getScore(scoreByMember) + 1, member);  
        return R.ok();  
    }  
  
    /**  
     * 获取成员分数值  
     *  
     * @param member 成员  
     * @return 分数值  
     */  
    @GetMapping("/get_score_by_member")  
    public R<Object> getScoreByMember(@RequestParam("member") String member) {  
        Double scoreByMember = RedisUtil.getScoreByMember(RedisKey.getRankingDemoKey(), member);  
        if (scoreByMember == null) {  
            return R.fail("该成员不存在");  
        }  
        RankingVo vo = new RankingVo();  
        vo.setMember(member);  
        vo.setScore(RankingUtil.getScore(scoreByMember));  
        vo.setTime(RankingUtil.getTimeStr(scoreByMember));  
        return R.ok(vo);  
    }  
  
}

Domain

import lombok.Data;  
  
/**  
 * @author Baisu  
 * @classname RankingVo  
 * @description 排行榜展示类  
 * @since 2024/5/6  
 */
@Data  
public class RankingVo {  
  
    /**  
     * 成员  
     */  
    private String member;  
    /**  
     * 分数值  
     */  
    private Long score;  
    /**  
     * 时间  
     */  
    private String time;  
  
}
/**  
 * @author Baisu  
 * @classname RedisKey  
 * @description Redis索引  
 * @since 2024/5/6  
 */
public class RedisKey {  
  
    private static final String RANKING_DEMO_KEY = "ranking_demo";  
  
    public static String getRankingDemoKey() {  
        return RANKING_DEMO_KEY;  
    }  
}
import cn.hutool.core.date.DateTime;  
import cn.hutool.core.date.DateUtil;  
import cn.hutool.extra.spring.SpringUtil;  
import com.ranking.demo.common.constant.RankingConstant;  
import org.redisson.api.RScoredSortedSet;  
import org.redisson.api.RedissonClient;  
import org.redisson.client.protocol.ScoredEntry;  
  
import java.util.Collection;  
  
/**  
 * @author Baisu  
 * @classname RedisUtil  
 * @description Redis工具类  
 * @since 2024/5/6  
 */
public class RedisUtil {  
  
    private static final RedissonClient REDISSON_CLIENT = SpringUtil.getBean(RedissonClient.class);  
  
    /**  
     * 向有序集合中添加指定分数的成员  
     *  
     * @param key    有序集索引  
     * @param score  分数  
     * @param member 成员  
     * @return 是否成功  
     */  
    public static boolean addScoreByMember(String key, Long score, String member) {  
        RScoredSortedSet<String> rScoredSortedSet = REDISSON_CLIENT.getScoredSortedSet(key);  
        double v = score * RankingConstant.BASIC_QUANTITY + (RankingConstant.MAXIMUM_TIME_TIMIT - Long.parseLong(DateUtil.format(DateTime.now(), RankingUtil.FORMAT)));  
        return rScoredSortedSet.add(v, member);  
    }  
  
    /**  
     * 返回有序集中成员的分数值  
     *  
     * @param key    有序集索引  
     * @param member 成员  
     * @return 分数值(Double)  
     */    
     public static Double getScoreByMember(String key, String member) {  
        RScoredSortedSet<Object> scoredSortedSet = REDISSON_CLIENT.getScoredSortedSet(key);  
        return scoredSortedSet.getScore(member);  
    }  
  
    /**  
     * 返回有序集中指定位置的成员集合  
     *  
     * @param key   有序集索引  
     * @param start 开始索引  
     * @param end   结束索引  
     * @return 成员集合  
     */  
    public static Collection<ScoredEntry<Object>> getRanking(String key, int start, int end) {  
        RScoredSortedSet<Object> rScoredSortedSet = REDISSON_CLIENT.getScoredSortedSet(key);  
        return rScoredSortedSet.entryRangeReversed(start, end);  
    }  
  
}
import cn.hutool.core.date.DateUtil;  
import com.ranking.demo.common.constant.RankingConstant;  
  
/**  
 * @author Baisu  
 * @classname RankingUtil  
 * @description 排行榜工具类  
 * @since 2024/5/7  
 */
 public class RankingUtil {  
  
    public static final String FORMAT = "yyyyMMddHHmmss";  
  
    public static Long getScore(Double score) {  
        return Math.round(Math.floor(score / RankingConstant.BASIC_QUANTITY));  
    }  
  
    public static String getTimeStr(Double score) {  
        return String.valueOf(DateUtil.parse(String.valueOf(RankingConstant.MAXIMUM_TIME_TIMIT - Math.round(Math.floor(score)) % RankingConstant.BASIC_QUANTITY)));  
    }  
}

4、接口文档

ranking_demo接口文档

以上就是SpringBoot+Redission实现排行榜功能的示例代码的详细内容,更多关于SpringBoot Redission排行榜的资料请关注脚本之家其它相关文章!

相关文章

  • Java实现使用Websocket发送消息详细代码举例

    Java实现使用Websocket发送消息详细代码举例

    这篇文章主要给大家介绍了关于Java实现使用Websocket发送消息的相关资料,WebSocket是一种协议,用于在Web应用程序和服务器之间建立实时、双向的通信连接,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-05-05
  • 实现一个基于Servlet的hello world程序详解步骤

    实现一个基于Servlet的hello world程序详解步骤

    Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层
    2022-02-02
  • Java使用策略模式解决商场促销商品问题示例

    Java使用策略模式解决商场促销商品问题示例

    这篇文章主要介绍了Java使用策略模式解决商场促销商品问题,简单描述了策略模式的概念、原理,并结合实例形式分析了Java基于策略模式解决商品促销问题的相关操作技巧,需要的朋友可以参考下
    2018-05-05
  • 堆排序实例(Java数组实现)

    堆排序实例(Java数组实现)

    下面小编就为大家分享一篇使用Java数组实现堆排序的实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2017-12-12
  • java Executors工具类的相关方法使用创建

    java Executors工具类的相关方法使用创建

    这篇文章主要为大家介绍了java Executors工具类的相关方法使用创建,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • windows如何使用bat脚本后台启动/停止和重启jar包服务

    windows如何使用bat脚本后台启动/停止和重启jar包服务

    这篇文章主要介绍了windows使用bat脚本后台启动/停止和重启jar包服务的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-11-11
  • 解决Java包装类比较时遇到的问题

    解决Java包装类比较时遇到的问题

    所谓包装类的作用就是将原始数据类型转换成引用数据类型,下面这篇文章主要给大家介绍了关于在Java包装类比较时遇到的问题的解决方法,文中给出了详细的示例代码,需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-09-09
  • 如何使用Java生成PDF文档详解

    如何使用Java生成PDF文档详解

    这篇文章主要给大家介绍了关于如何使用Java生成PDF文档的相关资料,PDF是可移植文档格式,是一种电子文件格式,具有许多其他电子文档格式无法相比的优点,需要的朋友可以参考下
    2023-07-07
  • java实现建造者模式(Builder Pattern)

    java实现建造者模式(Builder Pattern)

    这篇文章主要为大家详细介绍了java实现建造者模式Builder Pattern,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-10-10
  • Mybatis自定义拦截器实现权限功能

    Mybatis自定义拦截器实现权限功能

    本文主要介绍了Mybatis自定义拦截器实现权限功能,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-12-12

最新评论