SpringBoot继承Redis实现排行榜java
项目文件结构git
一、修改maven文件github
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.wu</groupId> <artifactId>rank</artifactId> <version>0.0.1-SNAPSHOT</version> <name>rank</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--redis的依赖包--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.38</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
二、redis配置文件web
spring.application.name=spring-boot-redis # 配置redis # Redis数据库索引(默认为0) spring.redis.database=0 # Redis服务器地址 spring.redis.host=127.0.0.1 # Redis服务器链接端口 spring.redis.port=6379 # Redis服务器链接密码(默认为空) spring.redis.password=foobared # 链接池最大链接数(使用负值表示没有限制) spring.redis.pool.max-active=8 # 链接池最大阻塞等待时间(使用负值表示没有限制) spring.redis.pool.max-wait=-1 # 链接池中的最大空闲链接 spring.redis.pool.max-idle=8 # 链接池中的最小空闲链接 spring.redis.pool.min-idle=0 # 链接超时时间(毫秒) spring.redis.timeout=200 # 这个地方须要
这里上面的redis的密码须要根据实际状况进行修改redis
三、RedisConfig文件spring
package com.wu.rank.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; @Configuration public class RedisConfig { @Bean public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisConnectionFactory) { StringRedisTemplate redis = new StringRedisTemplate(); redis.setConnectionFactory(redisConnectionFactory); // 设置redis的String/value的默认序列化方式 DefaultSerializer stringRedisSerializer = new DefaultSerializer(); redis.setKeySerializer(stringRedisSerializer); redis.setValueSerializer(stringRedisSerializer); redis.setHashKeySerializer(stringRedisSerializer); redis.setHashValueSerializer(stringRedisSerializer); redis.afterPropertiesSet(); return redis; } }
四、DefaultSerializer文件数据库
package com.wu.rank.config; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.SerializationException; import org.springframework.util.Assert; import java.nio.charset.Charset; public class DefaultSerializer implements RedisSerializer<Object> { private final Charset charset; public DefaultSerializer() { this(Charset.forName("UTF8")); } public DefaultSerializer(Charset charset) { Assert.notNull(charset, "Charset must not be null!"); this.charset = charset; } @Override public byte[] serialize(Object o) throws SerializationException { return o == null ? null : String.valueOf(o).getBytes(charset); } @Override public Object deserialize(byte[] bytes) throws SerializationException { return bytes == null ? null : new String(bytes, charset); } }
component文件夹apache
五、RedisComponent代码json
package com.wu.rank.component; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.ZSetOperations; import org.springframework.stereotype.Component; import java.util.Set; @Component public class RedisComponent { @Autowired private StringRedisTemplate redisTemplate; /** * 添加一个元素, zset与set最大的区别就是每一个元素都有一个score,所以有个排序的辅助功能; zadd * * @param key * @param value * @param score */ public void add(String key, String value, double score) { redisTemplate.opsForZSet().add(key, value, score); } /** * 删除元素 zrem * * @param key * @param value */ public void remove(String key, String value) { redisTemplate.opsForZSet().remove(key, value); } /** * score的增长or减小 zincrby * * @param key * @param value * @param score */ public Double incrScore(String key, String value, double score) { return redisTemplate.opsForZSet().incrementScore(key, value, score); } /** * 查询value对应的score zscore * * @param key * @param value * @return */ public Double score(String key, String value) { return redisTemplate.opsForZSet().score(key, value); } /** * 判断value在zset中的排名 zrank * * 积分小的在前面 * * @param key * @param value * @return */ public Long rank(String key, String value) { return redisTemplate.opsForZSet().rank(key, value); } /** * 查询集合中指定顺序的值, 0 -1 表示获取所有的集合内容 zrange * * 返回有序的集合,score小的在前面 * * @param key * @param start * @param end * @return */ public Set<String> range(String key, long start, long end) { return redisTemplate.opsForZSet().range(key, start, end); } /** * 查询集合中指定顺序的值和score,0, -1 表示获取所有的集合内容 * * @param key * @param start * @param end * @return */ public Set<ZSetOperations.TypedTuple<String>> rangeWithScore(String key, long start, long end) { return redisTemplate.opsForZSet().rangeWithScores(key, start, end); } /** * 查询集合中指定顺序的值 zrevrange * * 返回有序的集合中,score大的在前面 * * @param key * @param start * @param end * @return */ public Set<String> revRange(String key, long start, long end) { return redisTemplate.opsForZSet().reverseRange(key, start, end); } /** * 根据score的值,来获取知足条件的集合 zrangebyscore * * @param key * @param min * @param max * @return */ public Set<String> sortRange(String key, long min, long max) { return redisTemplate.opsForZSet().rangeByScore(key, min, max); } /** * 返回集合的长度 * * @param key * @return */ public Long size(String key) { return redisTemplate.opsForZSet().zCard(key); } }
六、RankListComponent服务器
package com.wu.rank.component; import com.wu.rank.model.RankDO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.ZSetOperations; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; @Component public class RankListComponent { @Autowired private RedisComponent redisComponent; private static final String RANK_PREFIX = "global_rank"; private List<RankDO> buildRedisRankToBizDO(Set<ZSetOperations.TypedTuple<String>> result, long offset) { List<RankDO> rankList = new ArrayList<>(result.size()); long rank = offset; for (ZSetOperations.TypedTuple<String> sub : result) { rankList.add(new RankDO(rank++, Math.abs(sub.getScore().floatValue()), Long.parseLong(sub.getValue()))); } return rankList; } /** * 获取前n名的排行榜数据 * * @param n * @return */ public List<RankDO> getTopNRanks(int n) { Set<ZSetOperations.TypedTuple<String>> result = redisComponent.rangeWithScore(RANK_PREFIX, 0, n - 1); return buildRedisRankToBizDO(result, 1); } /** * 获取用户所在排行榜的位置,以及排行榜中其先后n个用户的排行信息 * * @param userId * @param n * @return */ public List<RankDO> getRankAroundUser(Long userId, int n) { // 首先是获取用户对应的排名 RankDO rank = getRank(userId); if (rank.getRank() <= 0) { // fixme 用户没有上榜时,不返回 return Collections.emptyList(); } // 由于实际的排名是从0开始的,因此查询周边排名时,须要将n-1 Set<ZSetOperations.TypedTuple<String>> result = redisComponent.rangeWithScore(RANK_PREFIX, Math.max(0, rank.getRank() - n - 1), rank.getRank() + n - 1); return buildRedisRankToBizDO(result, rank.getRank() - n); } /** * 获取用户的排行榜位置 * * @param userId * @return */ public RankDO getRank(Long userId) { // 获取排行, 由于默认是0为开头,所以实际的排名须要+1 Long rank = redisComponent.rank(RANK_PREFIX, String.valueOf(userId)); if (rank == null) { // 没有排行时,直接返回一个默认的 return new RankDO(-1L, 0F, userId); } // 获取积分 Double score = redisComponent.score(RANK_PREFIX, String.valueOf(userId)); return new RankDO(rank + 1, Math.abs(score.floatValue()), userId); } /** * 更新用户积分,并获取最新的我的所在排行榜信息 * * @param userId * @param score * @return */ public RankDO updateRank(Long userId, Float score) { // 由于zset默认积分小的在前面,因此咱们对score进行取反,这样用户的积分越大,对应的score越小,排名越高 redisComponent.add(RANK_PREFIX, String.valueOf(userId), -score); Long rank = redisComponent.rank(RANK_PREFIX, String.valueOf(userId)); return new RankDO(rank + 1, score, userId); } }
Model文件夹
七、RankDO
package com.wu.rank.model; import java.io.Serializable; public class RankDO implements Serializable { private static final long serialVersionUID = 4804922606006935590L; /** * 排名 */ private Long rank; /** * 积分 */ private Float score; /** * 用户id */ private Long userId; public RankDO(Long rank, Float score, Long userId) { this.rank = rank; this.score = score; this.userId = userId; } public static long getSerialVersionUID() { return serialVersionUID; } public Long getRank() { return rank; } public void setRank(Long rank) { this.rank = rank; } public Float getScore() { return score; } public void setScore(Float score) { this.score = score; } public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } }
八、controller文件夹
RankAction
package com.wu.rank.controller; import com.wu.rank.component.RankListComponent; import com.wu.rank.model.RankDO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController public class RankAction { @Autowired private RankListComponent rankListComponent; @GetMapping(path = "/topn") public List<RankDO> showTopN(int n) { return rankListComponent.getTopNRanks(n); } @GetMapping(path = "/update") public RankDO updateScore(long userId, float score) { return rankListComponent.updateRank(userId, score); } @GetMapping(path = "/rank") public RankDO queryRank(long userId) { return rankListComponent.getRank(userId); } @GetMapping(path = "/around") public List<RankDO> around(long userId, int n) { return rankListComponent.getRankAroundUser(userId, n); } }
主程序代码
package com.wu.rank; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class RankApplication { public static void main(String[] args) { SpringApplication.run(RankApplication.class, args); } }
测试
一、先运行redis服务器
二、打开postman进行测试
参考:
一、https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-case/120-redis-ranklist