SpringBoot+Redission实现排行榜功能

devtools/2024/9/23 3:51:57/

SpringBoot+Redission实现排行榜功能

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

一、业务需求

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

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

1.简介

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

2.特点

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

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;  }
Utils
/**  * @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)));  }  
}

五、接口文档

ranking_demo接口文档

六、参考文章

Redis常用命令对应到Redisson对象操作_redisson rscript中eval-CSDN博客


http://www.ppmy.cn/devtools/39679.html

相关文章

外包干了6天,技术明显进步

先说一下自己的情况&#xff0c;本科生&#xff0c;2019年我通过校招踏入了南京一家软件公司&#xff0c;开始了我的职业生涯。那时的我&#xff0c;满怀热血和憧憬&#xff0c;期待着在这个行业中闯出一片天地。然而&#xff0c;随着时间的推移&#xff0c;我发现自己逐渐陷入…

让《行列视》解放数据力量,提升业务洞察

在当今信息化浪潮下&#xff0c;数据已经成为企业发展的核心驱动力之一。如何更好地管理和利用数据&#xff0c;已成为企业发展过程中亟需解决的问题之一。而报表工具作为数据可视化和分析的利器&#xff0c;正逐渐受到企业的重视和青睐。 一、《行列视》作为报表工具的重要性…

决策树的学习(Decision Tree)

1.对于决策树的概念&#xff1a; **本质上&#xff1a;**决策树就是模拟树的结构基于 if-else的多层判断 2.目的&#xff1a; 对实例进行分类的树形结构&#xff0c;通过多层判断&#xff0c;将所提供的数据归纳为一种分类规则。 3.优点&#xff1a; 1.计算量小&#xff0c;…

Vue3 - 修改浏览器标题 htmlWebpackPlugin.options.title 的值

在 Vue CLI 4.x 中&#xff0c;htmlWebpackPlugin.options.title 是 HtmlWebpackPlugin 的一个选项&#xff0c;用于设置生成的 HTML 文件的标题。 你可以通过修改 vue.config.js 文件来修改这个选项。 以下是一个示例&#xff1a; module.exports {chainWebpack: config &…

[力扣题解]51. N皇后

题目&#xff1a;51. N皇后 思路 回溯法 代码 class Solution { public:vector<vector<string>> result;bool right(int row, int col, int n, vector<string>& board){int i, j;// 不能同行, 这里我又没有写错&#xff0c;为什么结果不对呢&#xf…

uniap之微信公众号支付

近来用uniapp开发H5的时候&#xff0c;需要接入支付&#xff0c;原来都是基于后端框架来做的&#xff0c;所以可谓是一路坑中过&#xff0c;今天整理下大致流程分享给大家。 先封装util.js&#xff0c;便于后面调用 const isWechat function(){return String(navigator.userA…

36.Docker-Dockerfile自定义镜像

镜像结构 镜像是将应用程序及其需要的系统函数库、环境、配置、依赖打包而成。 镜像是分层机构&#xff0c;每一层都是一个layer BaseImage层&#xff1a;包含基本的系统函数库、环境变量、文件系统 EntryPoint:入口&#xff0c;是镜像中应用启动的命令 其他&#xff1a;在…

PHP数值数组讲解,for循环及函数 遍历数组获取元素

源码 <?phpheader("Content-Type:text/html;Charsetutf8");//创建数值数组$arr1 array();//简化创建语法 $arr2 [];//通过索引为数组添加不同类型的元素$arr1[0] "zhangsan" ;//也可以乱序添加元素$arr1[2] 12 ;$arr1[1] true ; //true输出为1 f…