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/