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