SpringBoot集成Redis實現排行榜

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 

相關文章
相關標籤/搜索