使用多種 Redis 資料型別構建一個文章投票網站

0Robert0發表於2020-06-24
<?php

/**
 * @description: 
 *     用hash"article:{$articleId}:content"儲存文章的內容、標題、標籤等
 *     用zset"article:time"儲存文章的釋出時間,作為釋出時間時間排行榜
 *     用zset"article:score"儲存文章的釋出時間和點贊累計後的綜合值,作為綜合評分排行榜
 *     用set"article:{$articleId}:vote"儲存給使用者點讚的使用者id,並設定一週的過期時間
 *     用set'article:tags'儲存所有的文章標籤
 *     用set"{$tag}:{$tagId}"儲存每一個文章標籤下的文章id
 * 
 * @author: 0Robert0
 */

class TestRedis
{
    const ADD_SCORE = 432;
    const EXPIRATION_TIME = 86400 * 7;
    private $redis;

    public function __construct()
    {
        $this->redis = new Redis();
        //連線引數:ip、埠、連線超時時間,連線成功返回true,否則返回false
        $this->redis->connect('127.0.0.1', 6379); 
    }

    // 釋出文章
    // 先獲取文章id;再建立文章釋出時間有序集合;建立文章綜合評分有序集合;
    // 然後建立為文章點讚的使用者集合,並把作者本身寫入進去,不允許自己給自己點贊,並設定一週的
    // 過期時間,為了記憶體考慮一週之後清掉使用者點贊集合
    // 設定文章標籤
    public function publishArticle($authorId, $tag)
    {
        // TODO 多個命令的串聯操作,應該開啟事務
        $articleId = $this->getId('article:id');
        $tagId = $this->getId('tag:id');
        $articleContent = [
            'title' => 'article title' . $articleId, 
            'author' => 'Rebort' . $articleId, 
            'content' => 'hello word',
            'tag' => $tag,
        ];
        $contentResult = $this->redis->hMset("article:{$articleId}:content", $articleContent);
        $timeResult = $this->redis->zAdd("article:time", time(), $articleId);
        $scoreResult = $this->redis->zAdd("article:score", time(), $articleId);
        $userResult = $this->redis->sAdd("article:{$articleId}:vote", $authorId);
        $expireResult = $this->redis->expire("article:{$articleId}:vote", self::EXPIRATION_TIME);
        $tagResult = $this->saveArticleTag($tag, $tagId);
        $userResult = $this->redis->sAdd("{$tag}:{$tagId}", $articleId);
        if ($articleId && $contentResult && $timeResult && $scoreResult && $userResult 
            && $expireResult && $tagResult) {
            return '釋出文章成功';
        } else {
            return '釋出文章失敗';
        }
    }

    // 給文章設定標籤;即把每一個文章根據標籤分類;如果標籤不存在,則建立標籤
    public function saveArticleTag($tag, $tagId)
    {
        $exists = $this->redis->sismember('article:tags', $tag . ':' . $tagId);
        if (!$exists) {
            return $this->redis->sAdd('article:tags', $tag . ':' . $tagId);
        }
    }

    // 按釋出時間展示指定範圍內的文章
    public function showArticlesOfReleaseTime($start, $end)
    {
        // 按釋出時間倒序排列
        $result = $this->redis->zRevRange('article:time', $start, $end, true);
        // $result = $redis->zRange('article:time', 0, -1, true); // 按釋出時間正序排列
        return $this->joinArticle($result);
    }

    // 按釋出時間和點贊數量綜合後的順序排列
    public function showArticlesOfLikeNumber($start, $end)
    {
        // 按釋出時間和點贊數量綜合後的倒序排列
        $result = $this->redis->zRevRange('article:score', $start, $end, true);
        // 按釋出時間和點贊數量綜合後的正序排列
        // $result = $redis->zRange('article:score', 0, -1, true); 
        return $this->joinArticle($result);
    }

    // 拼接文章內容
    public function joinArticle($articles)
    {
        $result = '';
        if (!empty($articles)) {
            foreach ($articles as $articleId => $value) {
                $articleHashKey = 'article:' . $articleId . ':content';
                $articleInfo = $this->redis->hGetAll($articleHashKey);
                $result .= 
                    '<div>'.
                        '<span>文章 id:' . $articleId . ' </span>' .
                        '<span>文章標題:'. $articleInfo['title'] . ' </span>' . 
                        '<span>文章內容:'. $articleInfo['content'] . ' </span>' .
                        '<span>文章作者:' . $articleInfo['author'] . ' </span>' .
                        '<span>文章標籤:' . $articleInfo['tag'] . ' </span>' .
                    '</div>';
            }
        } else {
            $result = '還未發表文章';
        }
        return $result;
    }

    // 點贊
    public function giveLike($userId, $articleId)
    {
        // 首先判斷文章是否存在以及釋出時間是否超過一週,超過一週的不允許點贊
        $publishTime = $this->redis->zScore('article:time', $articleId);
        $isOverdue = (time() - $publishTime) < self::EXPIRATION_TIME;
        $isClick = $this->isGiveLike($userId, $articleId);
        if (!empty($publishTime) && $isOverdue && !$isClick) {
            $addUserResult = $this->redis->sAdd("article:{$articleId}", $userId);
            $addScoreResult = $this->redis->zIncrBy('article:score', self::ADD_SCORE, $articleId);
            return ($addUserResult && $addScoreResult) ? '點贊成功' : '點贊失敗';
        }
        return '點贊失敗';
    }

    // 判斷使用者是否給這片文章點過贊
    public function isGiveLike($userId, $articleId)
    {
        $isClick = $this->redis->sismember("article:{$articleId}", $userId);
        if ($isClick) {
            $result = true;
        } else {
            $result = false;
        }
        return $result;
    }

    // 獲取主鍵id
    public function getId($key, $stepSize = 1)
    {
        return $this->redis->incrBy($key, $stepSize);
    }
}


$obj = new TestRedis();
var_dump($obj->publishArticle(999, 'Redis'));
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章