<?php
namespace app\social\extend;
use think\Exception;
class RedisLeaderboard
{
/**
*
* @var object redis client
*/
private $redis;
/**
*
* @var string 放置排行榜的key
*/
private $leaderboard;
private $_config;
//>>.分数后面加解决分数一样排名先后问题
private $key=9999999999999;
/**
* 初始化
* @param Array $config redis连接设定
*/
public function __construct($config=array(),$db=0,$leaderboard=''){
$this->_config = $config;
$this->redis = $this->connect($db);
if ($leaderboard) {
//这里不会检查当前的key值是否存在,是为了方便重新访问对应的排行榜
$this->leaderboard = $leaderboard;
} else {
$this->leaderboard = 'leaderboard:' . mt_rand(1, 100000);
// while (!empty($this->redis->exists($this->leaderboard))) {
// $this->leaderboard = 'leaderboard:' . mt_rand(1, 100000);
// }
}
}
/**
* 创建redis连接
* @return Link
*/
private function connect($db){
try{
$redis = new \Redis();
$redis->connect($this->_config['host']);
$redis->auth($this->_config['auth']);
empty($db)?'':$redis->select($db);
}catch(\RedisException $e){
throw new Exception($e->getMessage());
}
return $redis;
}
/**
* 获取当前的排行榜的key名
* @return string
*/
public function getLeaderboard()
{
return $this->leaderboard;
}
/**
* 将对应的值填入到排行榜中
* @param $node 对应的需要填入的值(比如商品的id)
* @param number $count 对应的分数,默认值为1
* @param bool $time 默认毫秒级
* @return Long 1 if the element is added. 0 otherwise.
*/
public function addLeaderboard($node, $count = 1,$time=true)
{
$count=$count.''.($this->key-$this->getMillisecond($time));//>>.解决分数一样的问题
return $this->redis->zAdd($this->leaderboard, $count, $node);
}
/**
* 默认毫秒
* 毫秒级
* @param $time
* @return float|int
*/
public function getMillisecond($time=true) {
if($time===false) return time();
list($s1, $s2) = explode(' ', microtime());
return (float)sprintf('%.0f', (floatval($s1) + floatval($s2)) * 1000);
}
/**
* 给出对应的排行榜
* @param int $number 需要给出排行榜数目
* @param bool $asc 排序顺序 true为按照高分为第0
* @param bool $withscores 是否需要分数
* @param callback $callback 用于处理排行榜的回调函数
* @return [] 对应排行榜
*/
public function getLeadboard($number, $asc = true, $withscores = false,$callback = null)
{
if ($asc) {
$nowLeadboard = $this->redis->zRevRange($this->leaderboard, 0, $number -1, $withscores);//按照高分数顺序排行;
} else {
$nowLeadboard = $this->redis->zRange($this->leaderboard, 0, $number -1, $withscores);//按照低分数顺序排行;
}
if ($callback) {
//使用回调处理
return $callback($nowLeadboard);
} else {
return $nowLeadboard;
}
}
//>>.获取前后排名
/**
* 给定ID获取排名
* @param $node
* @param int $c
* @return array|bool
*/
public function getRevRange($node,$c=5){
$ranking=$this->getNodeRank($node);
if($ranking===false) return false;
$start=$ranking-$c;
//>>.解决负数的问题
if($start<0) $start=0;
$end=$ranking+$c;
$ranking=$this->redis->zRevRange($this->leaderboard,$start,$end,true);
//>>.获取用户的名次
$data=[];
foreach ($ranking as $key=>$v){
$ranking_=$this->getNodeRank($key);
$v=$this->processingScores($v);
$data[$key]=['id'=>$key,'score'=>$v,'ranking'=>($ranking_+1),];
}
return $data;
}
/**
* 获取给定节点的排名
* @param string $node 对应的节点的key名
* @param string $asc 是否按照分数大小正序排名, true的情况下分数越大,排名越高
* @return 节点排名,根据$asc排序,true的话,第一高分为0,false的话第一低分为0
*/
public function getNodeRank($node, $asc = true)
{
if ($asc) {
//zRevRank 分数最高的排行为0,所以需要加1位
return $this->redis->zRevRank($this->leaderboard, $node);
} else {
return $this->redis->zRank($this->leaderboard, $node);
}
}
/**
* 获取分数,并且已经处理过的
* @param $node
* @return bool|float
*/
public function getScore($node){
$score=$this->redis->zScore($this->leaderboard, $node);
//>>.解决科学计算
$score=number_format($score,0,'','');
return $this->processingScores($score);
}
/**
* 处理分数
* @param $score
* @return false|string
*/
private function processingScores($score){
//>>.解决科学计算
$score=number_format($score,0,'','');
return substr($score, 0, -13);
}
}