thinkphp redis实现文章点赞功能并同步入mysql

<?php

namespace app\common\controller;

use think\App;
use think\facade\Cache;
use think\facade\Db;


/**
 * redis 点赞/收藏模块
 * @package app\admin\controller
 * @author 宁佳兵
 */
class Praise
{
    private $redis = null;
    private $member_id;   //用户id
    private $customer_id;   //客户id
    private $article_id;   //文章id
    private $article_type;  //文章类型
    private $status;    //点赞状态  1 点赞 0 取消点赞
    private $article_user_like;    //hash存放用户点赞记录
    private $article_user_like_set;    //集合存放该文章下点赞用户ID
    private $list_article; //队列存放article_id
    private $article_counts;   //文章点赞数
    private $praise_table = 'praise';

    /**
     * Praise constructor.
     * @param string $customer_id   客户id
     * @param string $member_id   用户id
     * @param string $article_id    文章id
     * @param string $article_type  文章类型/表名
     */
    public function __construct( $customer_id = '',  $member_id = '',  $article_id = '', $article_type = '')
    {
        $this->customer_id = $customer_id;
        $this->member_id = $member_id;
        $this->article_id = $article_id;
        $this->article_type = $article_type;
        $this->redis = Cache::store("redis");
        $this->list_article = 'list_article';
    }

    /**
     * 点赞入口
     * @return bool
     * @throws \Psr\SimpleCache\InvalidArgumentException
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     * @date 2020-07-17
     * @author 宁佳兵
     */
    public function giveFavour()
    {
        //判断用户点赞状态
        //先从缓存中读取数据,没有 则从mysql中读取数据
        $articleUserData = $this->getPraiseStatus();

        //存储点赞数据到redis
        $this->addArticleHash($articleUserData);

        //存储当前文章下的点赞用户id
        $this->addUser();

        //存储文章点赞数 自增/自减
        $this->articleLikeCount();

        //文章点赞数量
        $dbCount = $this->findMysqlCounts(); //mysql点赞数
        $redisCount = $this->findRedisCounts(); //redis点赞数
        $data['counts'] = (int)$dbCount + (int)$redisCount; //点赞总数
        $data['status'] = $this->status;

        //文章进入队列  等待同步到mysql
        $this->queueInto();

        return $data;
    }

    /**
     * 将redis 缓存 同步到 mysql中
     * @throws \Exception
     * @date 2020-07-17
     * @author 宁佳兵
     */
    public function syncInsertArticle()
    {
        $limit = input("limit");
        $limit = isset($limit) ? $limit - 1 : '-1';

        //取出队列
        $article = $this->queueOut($limit);

        //开启事务
        Db::startTrans();

        try {
            foreach ($article as $key => $val) {
                list($this->article_id, $this->article_type, $this->customer_id, $this->member_id) = explode('_', $val);
                //获取redis中该文章下所有用户id
                $user = $this->findUserByNews();
                //获取redis中文章点赞数
                $redisNum = $this->findRedisCounts();

                //更新mysql中文章点赞数
                $this->updateDbCount($redisNum);
                //获取该文章中的用户,循环更新数据到mysql

                foreach ($user as $k => $v) {
                    $this->member_id = $v;
                    //查询该文章hash数据
                    $userSatateData = $this->findArticleHash();
                    //更新mysql中eda_zan表数据
                    $this->updateDbArticle($userSatateData);
                    //删除该文章下redis中的用户
                    $this->unsetRedisUserCounts();
                    //删除redis中该文章hash数据
                    $this->unsetRedisArticleHash();
                }
            }
        } catch (\Exception $exception) {
            //事务回滚
            Db::rollback();
            //写入日志
            $this->writeLog();
            throw new \Exception($exception->getMessage());
        }
        //提交事务
        Db::commit();
        //TODO 这里暂时删除全部队列,后续需要完善
        $this->redis->del($this->list_article);
        echo 'success';
    }

    /**
     * 点赞总数
     * @return array
     * @throws \Psr\SimpleCache\InvalidArgumentException
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     * @date 2020-07-20
     * @author 宁佳兵
     */
    public function getPraiseCounts()
    {
        $this->getPraiseStatus();
        return [
            'counts' => ((int)$this->findMysqlCounts() + (int)$this->findRedisCounts()),
            'status' => $this->status ? 0 : 1
        ]; //点赞总数
    }

    /**
     * 判断用户点赞状态  先从缓存中读取数据,没有 则从mysql中读取数据
     * @return bool
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     * @date 2020-07-20
     * @author 宁佳兵
     */
    private function getPraiseStatus()
    {
        if ($articleUserData = $this->findArticleHash()) {
            $this->status = $articleUserData['status'] ? 0 : 1;
        } else {
            if ($result = $this->findDbArticle()) {
                $this->status = $result['status'] ? 0 : 1;
            } else {
                $this->status = 1;
            }
        }
        return $articleUserData;
    }

    /**
     * 获取redis中用户点赞状态数据
     * @return bool
     * @date 2020-07-17
     * @author 宁佳兵
     */
    private function findArticleHash()
    {
        $this->article_user_like = 'article_user_like_' . $this->customer_id . '_' . $this->member_id . '_' . $this->article_id . '_' . $this->article_type;
        return $this->redis->hGetAll($this->article_user_like) ?: false;
    }

    /**
     * 获取mysql中用户点赞状态
     * @return array|bool|\think\Model|null
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     * @date 2020-07-17
     * @author 宁佳兵
     */
    private function findDbArticle()
    {

        if (empty($this->article_type)) {
            return false;
        }

        $data = Db::name($this->praise_table)
            ->where([
                "c_id" => $this->customer_id,
                "member_id" => $this->member_id,
                "article_id" => $this->article_id,
                "type" => $this->article_type,
            ])
            ->find();

        return $data ?: false;
    }

    /**
     * 点赞数据写入hash
     * @param array $articleUserData
     * @return bool
     * @date 2020-07-17
     * @author 宁佳兵
     */
    private function addArticleHash($articleUserData = [])
    {
        $this->article_user_like = 'article_user_like_' . $this->customer_id . '_' . $this->member_id . '_' . $this->article_id . '_' . $this->article_type;

        if (!$articleUserData || empty($articleUserData)) {
            $this->redis->hSet($this->article_user_like, 'article_id', $this->article_id);  //文章id
            $this->redis->hSet($this->article_user_like, 'member_id', $this->member_id);    //用户id
            $this->redis->hSet($this->article_user_like, 'customer_id', $this->customer_id);    //客户id
            $this->redis->hSet($this->article_user_like, 'article_type', $this->article_type);  //点赞类型
            $this->redis->hSet($this->article_user_like, 'create_time', time());    //点赞时间
        }
        $this->redis->hSet($this->article_user_like, 'update_time', time());    //更新时间
        $this->redis->hSet($this->article_user_like, 'status', $this->status);  //点赞状态
        return true;
    }

    /**
     * 当前文章下的点赞用户id
     * @return mixed
     * @date 2020-07-17
     * @author 宁佳兵
     */
    private function addUser()
    {
        $this->article_user_like_set = 'article_user_like_set_' . $this->article_id . '_' . $this->article_type;
        return $this->redis->sAdd($this->article_user_like_set, $this->member_id);
    }

    /**
     * 文章点赞数计数
     * @return mixed
     * @date 2020-07-17
     * @author 宁佳兵
     */
    private function articleLikeCount()
    {
        $this->article_counts = 'article_counts_' . $this->article_id . '_' . $this->article_type;

        if ($this->status) {
            //点赞数自增
            $counts = $this->redis->incr($this->article_counts);
        } else {
            //点赞数自减
            $counts = $this->redis->decr($this->article_counts);
        }

        return $counts;
    }

    /**
     * 文章入队列
     * @date 2020-07-17
     * @author 宁佳兵
     */
    private function queueInto()
    {
        return $this->redis->rPush($this->list_article, $this->article_id . '_' . $this->article_type . '_' . $this->customer_id . '_' . $this->member_id);
    }

    /**
     * 文章出队列
     * @param $limit
     * @return mixed
     * @date 2020-07-17
     * @author 宁佳兵
     */
    private function queueOut($limit = '-1')
    {
        return $this->redis->Lrange($this->list_article, 0, $limit);
    }

    /**
     * 获取redis中 文章的点赞数量
     * @return int|mixed
     * @throws \Psr\SimpleCache\InvalidArgumentException
     * @date 2020-07-17
     * @author 宁佳兵
     */
    private function findRedisCounts()
    {
        $this->article_counts = 'article_counts_' . $this->article_id . '_' . $this->article_type;
        return $this->redis->get($this->article_counts) ?: 0;
    }

    /**
     * 获取mysql文章的点赞数量
     * @return mixed
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     * @date 2020-07-20
     * @author 宁佳兵
     */
    private function findMysqlCounts()
    {
        return Db::name($this->article_type)
            ->where([
                'id' => $this->article_id,
            ])
            ->find()['fabulous'];
    }

    /**
     * 获取redis中该文章下所有用户id
     * @return mixed
     * @date 2020-07-17
     * @author 宁佳兵
     */
    private function findUserByNews()
    {
        $this->post_user_like_set = 'article_user_like_set_' . $this->article_id . '_' . $this->article_type;
        return $this->redis->sMembers($this->post_user_like_set);
    }


    /**
     * 更新mysql文章点赞数
     * @param $redisNum
     * @return bool|mixed
     * @throws \think\db\exception\DbException
     * @date 2020-07-17
     * @author 宁佳兵
     */
    private function updateDbCount($redisNum)
    {
        //inc 方法不起作用  暂时注释
//        $result = Db::name($this->article_type)
//            ->where([
//                'id' => $this->article_id,
//            ])
//            ->inc('fabulous', (int)$redisNum);

        $fabulous = Db::name($this->article_type)
            ->where([
                'id' => $this->article_id,
            ])
            ->find()['fabulous'];

        $result = Db::name($this->article_type)
            ->where([
                'id' => $this->article_id,
            ])
            ->update(['fabulous' => (int)$fabulous + (int)$redisNum]);

        return $result ? $this->unsetRedisArticleCounts() : false;
    }

    /**
     * 更新mysql点赞表
     * @param array $userSatateData
     * @return bool
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     * @date 2020-07-17
     * @author 宁佳兵
     */
    private function updateDbArticle($userSatateData = [])
    {
        //判断用户原来是否点赞过
        if ($this->findDbArticle() === false) {
            $data = Db::name($this->praise_table)
                ->insert([
                    'article_id' => $userSatateData['article_id'],
                    'type' => $userSatateData['article_type'],
                    'c_id' => $userSatateData['customer_id'],
                    'member_id' => $userSatateData['member_id'],
                    'modified' => $userSatateData['update_time'],
                    'status' => $userSatateData['status'],  //点赞
                ]);
        }else{
            $data = Db::name($this->praise_table)
                ->where([
                    'article_id' => $userSatateData['article_id'],
                    'type' => $userSatateData['article_type'],
                    'c_id' => $userSatateData['customer_id'],
                    'member_id' => $userSatateData['member_id'],
                ])
                ->update([
                    'modified' => $userSatateData['update_time'],
                    'status' => $userSatateData['status'],  //取消点赞
                ]);
        }

        return ! empty($data) ? $data : false;
    }


    /**
     * 删除redis文章点赞数
     * @return mixed
     * @date 2020-07-17
     * @author 宁佳兵
     */
    private function unsetRedisArticleCounts()
    {
        $this->article_counts = 'article_counts_' . $this->article_id . '_' . $this->article_type;
        return $this->redis->del($this->article_counts);
    }

    /**
     * 删除该文章下redis中的用户
     * @return mixed
     * @date 2020-07-17
     * @author 宁佳兵
     */
    private function unsetRedisUserCounts()
    {
        $this->article_user_like_set = 'article_user_like_set_' . $this->article_id . '_' . $this->article_type;
        return $this->redis->sRem($this->article_user_like_set, $this->member_id);
    }

    /**
     * 删除redis中该文章hash数据
     * @return mixed
     * @date 2020-07-17
     * @author 宁佳兵
     */
    private function unsetRedisArticleHash()
    {
        $this->article_user_like = 'article_user_like_' . $this->customer_id . '_' . $this->member_id . '_' . $this->article_id . '_' . $this->article_type;
        return $this->redis->del($this->article_user_like);
    }


    /**
     * 记录错误日志
     * @date 2020-07-17
     * @author 宁佳兵
     */
    private function writeLog()
    {
        $file = '../runtime/log/redis.log';
        $content = "操作失败,文章:" . $this->article_id . '---用户:' . $this->member_id . '\r\n';
        if (is_file($file)) {
            file_put_contents($file, $content, FILE_APPEND);
        }
    }

}

 可参考https://blog.csdn.net/MyDream229/article/details/107363287/

posted @ 2020-07-20 14:48  宁佳兵  阅读(1093)  评论(0编辑  收藏  举报