基于springboot开发,Redis的Zset类型实现排行榜

Redis的Zset类型实现排行榜

实现截图:
基于springboot开发,Redis的Zset类型实现排行榜_第1张图片

项目结构:
基于springboot开发,Redis的Zset类型实现排行榜_第2张图片
yml文件配置:连接Redis

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: 123456

配置Redis序列化规则:

package com.example.demo.config;

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;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {
     

    @Bean
    public RedisTemplate<String,String> redisTemplate(RedisConnectionFactory redisConnectionFactory){
     
        StringRedisTemplate redis = new StringRedisTemplate();
        redis.setConnectionFactory(redisConnectionFactory);

        StringRedisSerializer serializer = new StringRedisSerializer();
        redis.setKeySerializer(serializer);
        redis.setValueSerializer(serializer);
        redis.setHashKeySerializer(serializer);
        redis.setHashValueSerializer(serializer);

        return redis;
    }
}

创建实体类:

package com.example.demo.entity;

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;
    }
}

排行榜ScoreService接口:

package com.example.demo.service;

import com.example.demo.entity.RankDO;

import java.util.List;

public interface ScoreService {
     

    /**
     * 获取前n名的排行榜数据
     *
     * @param num
     * @return
     */
    List<RankDO> findTopByNum(int num);

    /**
     * 插入用户数据
     * @param userId
     * @param score
     * @return
     */
    RankDO updateRank(long userId,float score);

    /**
     * 获取用户的排行榜位置
     *
     * @param userId
     * @return
     */
    RankDO getRankDo(long userId);

    /**
     * 获取用户所在排行榜的位置,以及排行榜中其前后n个用户的排行信息
     *
     * @param userId
     * @param n
     * @return
     */
    List<RankDO> getRankAroundUser(Long userId, int n);
}

实现接口ScoreServiceImpl:

package com.example.demo.service;

import com.example.demo.entity.RankDO;
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.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

@Service
public class ScoreServiceImpl implements ScoreService {
     

    @Autowired
    StringRedisTemplate redisTemplate;

    @Override
    public List<RankDO> findTopByNum(int num) {
     
        //查询集合中指定顺序的值和score,0, -1 表示获取全部的集合内容
        Set<ZSetOperations.TypedTuple<String>> tupleSet = redisTemplate.opsForZSet().rangeWithScores("global_rank", 0, num-1);
        List<RankDO> rankList = new ArrayList<>(tupleSet.size());
        long rank = 1;
        for (ZSetOperations.TypedTuple<String> sub : tupleSet) {
     
            rankList.add(new RankDO(rank++, Math.abs(sub.getScore().floatValue()), Long.parseLong(sub.getValue())));
        }
        return rankList;
    }

    @Override
    public RankDO updateRank(long userId, float score) {
     
        //添加一个元素, zset与set最大的区别就是每个元素都有一个score,因此有个排序的辅助功能;  zadd
        redisTemplate.opsForZSet().add("global_rank", String.valueOf(userId), -score);
        Long rank = redisTemplate.opsForZSet().rank("global_rank", String.valueOf(userId));
        return new RankDO(rank + 1,score,userId);
    }

    @Override
    public RankDO getRankDo(long userId) {
     
        // 获取排行, 因为默认是0为开头,因此实际的排名需要+1
        Long rank = redisTemplate.opsForZSet().rank("global_rank", String.valueOf(userId));
        if (rank == null) {
     
            // 没有排行时,直接返回一个默认的
            return new RankDO(-1L, 0F, userId);
        }

        // 获取积分
        Double score = redisTemplate.opsForZSet().score("global_rank", String.valueOf(userId));
        return new RankDO(rank + 1, Math.abs(score.floatValue()), userId);
    }

    @Override
    public List<RankDO> getRankAroundUser(Long userId, int n) {
     
        // 首先是获取用户对应的排名
        RankDO rankDo = getRankDo(userId);
        if (rankDo.getRank() <= 0) {
     
            // fixme 用户没有上榜时,不返回
            return Collections.emptyList();
        }

        // 因为实际的排名是从0开始的,所以查询周边排名时,需要将n-1
        // 查询集合中指定顺序的值和score,0, -1 表示获取全部的集合内容
        Set<ZSetOperations.TypedTuple<String>> result =
                redisTemplate.opsForZSet().rangeWithScores("global_rank", Math.max(0, rankDo.getRank() - n - 1), rankDo.getRank() + n - 1);
        List<RankDO> rankList = new ArrayList<>(result.size());
        long rank = rankDo.getRank() - n;
        for (ZSetOperations.TypedTuple<String> sub : result) {
     
            rankList.add(new RankDO(rank++, Math.abs(sub.getScore().floatValue()), Long.parseLong(sub.getValue())));
        }
        return rankList;
    }
}

控制层RankController :

package com.example.demo.controller;

import com.example.demo.entity.RankDO;
import com.example.demo.service.ScoreService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

import java.util.List;

@RestController
public class RankController {
     

    @Autowired
    ScoreService scoreService;

    @RequestMapping(value = "/index",method = RequestMethod.GET)
    public ModelAndView index(){
     
        return new ModelAndView("/index");
    }


    /**
     * 获取前n名的排行榜数据
     * @param n
     * @return
     */
    @GetMapping(path = "/topn")
    public List<RankDO> showTopN(int n) {
     
        return scoreService.findTopByNum(n);
    }

    /**
     * 插入新的数据排名
     * @param userId
     * @param score
     * @return
     */
    @GetMapping(path = "/update")
    public RankDO updateScore(long userId, float score) {
     
        return scoreService.updateRank(userId,score);
    }

    /**
     * 获取用户的排行榜位置
     * @param userId
     * @return
     */
    @GetMapping(path = "/rank")
    public RankDO queryRank(long userId) {
     
        return scoreService.getRankDo(userId);
    }

    /**
     * 获取用户所在排行榜的位置,以及排行榜中其前后n个用户的排行信息
     * @param userId
     * @param n
     * @return
     */
    @GetMapping(path = "/around")
    public List<RankDO> around(long userId, int n) {
     
        return scoreService.getRankAroundUser(userId,n);
        //return rankListComponent.getRankAroundUser(userId, n);
    }

}

前端代码实现index.html:


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
<div id="redis">
    Redis排行榜(前五名)
    <table border="1">
        <tr>
            <td>usertd>
            <td>scoretd>
        tr>
        <tr v-for="user in ranks">
            <td>{
    {user.userId}}td>
            <td>{
    {user.score}}td>
        tr>
    table>
    <input placeholder="用户id" v-model="user.userId" style="width: 50px"/>
    <input placeholder="分数" v-model="user.score" style="width: 50px"/>
    <button @click="doSave()">提交button>
    <div>
        <input placeholder="用户id" v-model="userId" style="width: 50px"/>
        <input placeholder="排名" v-model="no" style="width: 50px"/>
        <button @click="findNo">查询button>
    div>
div>

<script src="/jquery-2.2.3.min.js">script>
<script src="/vue-2.6.11.js">script>
<script src="/axios-0.19.2.min.js">script>
<script>
    let v = new Vue({
      
        el:'#redis',
        data:{
      
            userId:'',
            user:{
      },
            ranks:{
      },
            no:''
        },
        methods:{
      
            showTopN:function (n) {
      
                axios({
      
                    url:'/topn',
                    method:'get',
                    params:{
      
                        'n':n
                    }
                }).then(response=>{
      
                    this.ranks = response.data;
                    this.user={
      };
                })
            },
            doSave:function () {
      
                axios({
      
                    url:'/update',
                    params: {
      
                        "userId":this.user.userId,
                        "score":this.user.score
                    }
                }).then(response=>{
      
                    this.showTopN(5);
                })
            },
            findNo:function () {
      
                axios({
      
                    url:'/rank',
                    params:{
      
                        "userId":this.userId
                    }
                }).then(response=>{
      
                    this.no = response.data.rank;
                })
            }
        },
        created:function () {
      
            this.showTopN(5);
        }
    })
script>
body>
html>

你可能感兴趣的:(Redis,spring框架,springboot,redis,spring)