<?php
/**
* Class Vote
*
* 實現業務邏輯:一個文章投票系統:每一篇文章的投票期限是一週,文章釋出超過一週後無法投票,
* 釋出的文章如果每天大於等於200票就認為是一個有趣的文章,就應該在顯示固定名次的分數排行榜上至少多排一天。
*/
class Vote
{
private $redis;
const ONE_WEEK_IN_SECONDS = 604800;
const VOTE_SCORE = 432; //每天86400/200 得到432
const ARTICLE_PAGE = 100; //每頁顯示的文章
private static $instance;
public static function getInstance()
{
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
//連結redis初始化
public function __construct()
{
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', '6379', '3');
}
//釋出文章
public function postArticle($user, $title)
{
//用一個計數器不斷的增加生成文章的id
$articleId = (string)$this->redis->incr('article:');
$vote = 'vote:' . $articleId;
$this->redis->sAdd($vote, $user); //用一個set儲存對文章投票的使用者,並把文章的建立者放入集合裡面
$this->redis->expire($vote, self::ONE_WEEK_IN_SECONDS);//設定投票的集合的有效期
$article = 'article:' . $articleId;
$link = "http://www.article.com?id=" . $articleId;
$articleData = [
'votes' => 1,
'user' => $user,
'title' => $title,
'link' => $link
];
//生成文章的資訊,並按照規則把每個文章存入hash裡面
$this->redis->hMset($article, $articleData);
//在zset time裡面記錄文章的釋出時間為現在
$this->redis->zAdd('time', time(), $article);
//在zset score裡面增加文章的分數
$this->redis->zAdd('score', time() +
self::VOTE_SCORE, $article);
}
/**
* 按時間順序或者投票和時間綜合順序獲取文章
* @param $page
* @param string $order
* @return array
*/
public function getArticle($page, $order = 'score')
{
$start = ($page - 1) * self::ARTICLE_PAGE;
$end = $start + self::ARTICLE_PAGE - 1;
//逆向獲取對應zset集合裡面範圍內的文章
$ids = $this->redis->zRevRange($order, $start, $end);
$articles = [];
foreach ($ids as $id) {
//根據值在用hash獲取資料
$articleData = $this->redis->hGetAll($id);
$articles[] = $articleData;
}
return $articles;
}
/**
* @param $user 使用者給文章投票
* @param $article
*/
public function articleVote($user, $articleId)
{
$article = "article:" . $articleId;
$cutoff = time() - self::ONE_WEEK_IN_SECONDS;
/**
* time是一個zset,有序結合,time是集合的名字,
*member是根據規則"article:id"拼接出的字串,score
* 就是文章的釋出時間
* time用來記錄每個文章和文章對應的釋出時間
*/
if ($this->redis->zScore('time', $article) < $cutoff) { //時間過期了就返回
return;
}
/*** 每個文章用一個單獨的set記錄投票的使用者,集合命名
* 規則是"vote:文章id",把投票的使用者加入到這個集合裡面
*/
if ($this->redis->sAdd('voted:' . $articleId, $user)) {
//對文章的votes值加1
$this->redis->hIncrBy($article, 'votes', 1);
//給用來記錄文章分數的zset裡面對對應文章增加分數
$this->redis->zIncrBy('score', self::VOTE_SCORE, $article);
}
}
}