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>