PHP使用redis位图bitMap 实现签到功能


Posted in PHP onOctober 08, 2019

一、需求

记录用户签到,查询用户签到

二、技术方案

1、使用mysql(max_time字段为连续签到天数)

PHP使用redis位图bitMap 实现签到功能 

思路:

(1)用户签到,插入一条记录,根据create_time查询昨日是否签到,有签到则max_time在原基础+1,否则,max_time=0

(2)检测签到,根据user_id、create_time查询记录是否存在,不存在则表示未签到

2、使用redis位图功能

思路:

(1)每个用户每个月单独一条redis记录,如00101010101010,从左往右代表01-31天(每月有几天,就到几天)
(2)每月8号凌晨,统一将redis的记录,搬至mysql,记录如图

PHP使用redis位图bitMap 实现签到功能 

(3)查询当月,从redis查,上月则从mysql获取

3、方案对比

举例:一万个用户签到365天

方案1、mysql 插入365万条记录

· 频繁请求数据库做一些日志记录浪费服务器开销。
·  随着时间推移数据急剧增大
· 海量数据检索效率也不高,同时只能用时间create_time作为区间查询条件,数据量大肯定慢

方案2、mysql 插入12w条记录

· 节省空间,每个用户每天只占用1bit空间 1w个用户每天产生10000bit=1050byte 大概为1kb的数据
· 内存操作存度快

3、实现(方案2)

(1)key结构

前缀_年份_月份:用户id -- sign_2019_10:01

查询:

单个:keys sign_2019_10_01

全部:keys sign_*

月份:keys sign_2019_10:*

(2)mysql表结构

PHP使用redis位图bitMap 实现签到功能 

(3)代码(列出1个调用方法,与三个类)

·签到方法

public static function userSignIn($userId)
  {
    $time = Time();
    $today = date('d', $time);
    $year = date('Y', $time);
    $month = date('m', $time);
    $signModel = new Sign($userId,$year,$month);
    //1、查询用户今日签到信息
    $todaySign = $signModel->getSignLog($today);
    if ($todaySign) {
      return self::jsonArr(-1, '您已经签到过了', []);
    }
    try {
      Db::startTrans();
      $signModel->setSignLog($today);
      //4、赠送积分
      if (self::SING_IN_SCORE > 0) {
        $dataScore['order_id'] = $userId.'_'.$today;
        $dataScore['type'] = 2;//2、签到
        $dataScore['remark'] = '签到获得积分';
        Finance::updateUserScore(Finance::OPT_ADD, $userId, self::SING_IN_SCORE, $dataScore);
      }
      $code = '0';
      $msg = '签到成功';
      $score = self::SING_IN_SCORE;
      Db::commit();
    } catch (\Exception $e) {
      Db::rollback();
      $code = '-2';
      $msg = '签到失败';
      $score = 0;
    }
    return self::jsonArr($code, $msg, ['score' => $score]);
  }

·redis基类

<?php
namespace app\common\redis\db1;
/**
 * redis操作类
 */
class RedisAbstract
{
  /**
   * 连接的库
   * @var int
   */
  protected $_db = 1;//数据库名
  protected $_tableName = '';//表名
  static $redis = null;
  public function __construct()
  {
    return $this->getRedis();
  }
  public function _calcKey($id)
  {
    return $this->_tableName . $id;
  }
  /**
   * 查找key
   * @param $key
   * @return array
   * @throws \Exception
   * @author wenzhen-chen
   */
  public function keys($key)
  {
    return $this->getRedis()->keys($this->_calcKey($key));
  }
  /**
   * 获取是否开启缓存的设置参数
   *
   * @return boolean
   */
  public function _getEnable()
  {
    $conf = Config('redis');
    return $conf['enable'];
  }
  /**
   * 获取redis连接
   *
   * @staticvar null $redis
   * @return \Redis
   * @throws \Exception
   */
  public function getRedis()
  {
    if (!self::$redis) {
      $conf = Config('redis');
      if (!$conf) {
        throw new \Exception('redis连接必须设置');
      }
      self::$redis = new \Redis();
      self::$redis->connect($conf['host'], $conf['port']);
      self::$redis->select($this->_db);
    }
    return self::$redis;
  }
  /**
   * 设置位图
   * @param $key
   * @param $offset
   * @param $value
   * @param int $time
   * @return int|null
   * @throws \Exception
   * @author wenzhen-chen
   */
  public function setBit($key, $offset, $value, $time = 0)
  {
    if (!$this->_getEnable()) {
      return null;
    }
    $result = $this->getRedis()->setBit($key, $offset, $value);
    if ($time) {
      $this->getRedis()->expire($key, $time);
    }
    return $result;
  }
  /**
   * 获取位图
   * @param $key
   * @param $offset
   * @return int|null
   * @throws \Exception
   * @author wenzhen-chen
   */
  public function getBit($key, $offset)
  {
    if (!$this->_getEnable()) {
      return null;
    }
    return $this->getRedis()->getBit($key, $offset);
  }
  /**
   * 统计位图
   * @param $key
   * @return int|null
   * @throws \Exception
   * @author wenzhen-chen
   */
  public function bitCount($key)
  {
    if (!$this->_getEnable()) {
      return null;
    }
    return $this->getRedis()->bitCount($key);
  }
  /**
   * 位图操作
   * @param $operation
   * @param $retKey
   * @param mixed ...$key
   * @return int|null
   * @throws \Exception
   * @author wenzhen-chen
   */
  public function bitOp($operation, $retKey, ...$key)
  {
    if (!$this->_getEnable()) {
      return null;
    }
    return $this->getRedis()->bitOp($operation, $retKey, $key);
  }
  /**
   * 计算在某段位图中 1或0第一次出现的位置
   * @param $key
   * @param $bit 1/0
   * @param $start
   * @param null $end
   * @return int|null
   * @throws \Exception
   * @author wenzhen-chen
   */
  public function bitPos($key, $bit, $start, $end = null)
  {
    if (!$this->_getEnable()) {
      return null;
    }
    return $this->getRedis()->bitpos($key, $bit, $start, $end);
  }
  /**
   * 删除数据
   * @param $key
   * @return int|null
   * @throws \Exception
   * @author wenzhen-chen
   */
  public function del($key)
  {
    if (!$this->_getEnable()) {
      return null;
    }
    return $this->getRedis()->del($key);
  }
}

·签到redis操作类

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2019/9/30
 * Time: 14:42
 */
namespace app\common\redis\db1;
class Sign extends RedisAbstract
{
  public $keySign = 'sign';//签到记录key
  public function __construct($userId,$year,$month)
  {
    parent::__construct();
    //设置当前用户 签到记录的key
    $this->keySign = $this->keySign . '_' . $year . '_' . $month . ':' . $userId;
  }
  /**
   * 用户签到
   * @param $day
   * @return int|null
   * @throws \Exception
   * @author wenzhen-chen
   */
  public function setSignLog($day)
  {
    return $this->setBit($this->keySign, $day, 1);
  }
  /**
   * 查询签到记录
   * @param $day
   * @return int|null
   * @throws \Exception
   * @author wenzhen-chen
   */
  public function getSignLog($userId,$day)
  {
    return $this->getBit($this->keySign, $day);
  }
  /**
   * 删除签到记录
   * @return int|null
   * @throws \Exception
   * @author wenzhen-chen
   */
  public function delSignLig()
  {
    return $this->del($this->keySign);
  }
}

· 定时更新至mysql的类

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2019/10/4
 * Time: 19:03
 */
namespace app\common\business;
use app\common\mysql\SignLog;
use app\common\redis\db1\Sign;
class Cron
{
  /**
   * 同步用户签到记录
   * @throws \Exception
   */
  public static function addUserSignLogToMysql()
  {
    $data = [];
    $time = Time();
    //1、计算上月的年份、月份
    $dataTime = Common::getMonthTimeByKey(0);
    $year = date('Y', $dataTime['start_time']);
    $month = date('m', $dataTime['start_time']);
    //2、查询签到记录的key
    $signModel = new Sign(0, $year, $month);
    $keys = $signModel->keys('sign_' . $year . '_' . $month . ':*');
    foreach ($keys as $key) {
      $bitLog = '';//用户当月签到记录
      $userData = explode(':', $key);
      $userId = $userData[1];
      //3、循环查询用户是否签到(这里没按每月天数存储,直接都存31天了)
      for ($i = 1; $i <= 31; $i++) {
        $isSign = $signModel->getBit($key, $i);
        $bitLog .= $isSign;
      }
      $data[] = [
        'user_id' => $userId,
        'year' => $year,
        'month' => $month,
        'bit_log' => $bitLog,
        'create_time' => $time,
        'update_time' => $time
      ];
    }
    //4、插入日志
    if ($data) {
      $logModel = new SignLog();
      $logModel->insertAll($data, '', 100);
    }
  }
}

总结

以上所述是小编给大家介绍的PHP使用redis位图bitMap 实现签到功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

PHP 相关文章推荐
提高PHP编程效率的53个要点(经验小结)
Sep 04 PHP
PHP中将网页导出为Word文档的代码
May 25 PHP
php配置php-fpm启动参数及配置详解
Nov 04 PHP
PHP实现微信公众平台音乐点播
Mar 20 PHP
用PHP代替JS玩转DOM的思路及示例代码
Jun 15 PHP
PHP源码分析之变量的存储过程分解
Jul 03 PHP
朋友网关于QQ相关的PHP代码(研究QQ的绝佳资料)
Jan 26 PHP
PHP扩展迁移为PHP7扩展兼容性问题记录
Feb 15 PHP
PHP 验证身份证是否合法的函数
Feb 09 PHP
PHP 实现浏览记录并按日期分组
May 11 PHP
微信JSSDK分享功能图文实例详解
Apr 08 PHP
Laravel使用模型实现like模糊查询的例子
Oct 24 PHP
laravel-admin自动生成模块,及相关基础配置方法
Oct 08 #PHP
laravel-admin表单提交隐藏一些数据,回调时获取数据的方法
Oct 08 #PHP
关于Laravel-admin的基础用法总结和自定义model详解
Oct 08 #PHP
laravel-admin 实现给grid的列添加行数序号的方法
Oct 08 #PHP
PHP实现数组根据某个字段进行水平合并,横向合并案例分析
Oct 08 #PHP
thinkphp5框架前后端分离项目实现分页功能的方法分析
Oct 08 #PHP
PHP7 安装event扩展的实现方法
Oct 08 #PHP
You might like
基于php验证码函数的使用示例
2013/05/03 PHP
php实现的DateDiff和DateAdd时间函数代码分享
2014/08/16 PHP
jquery插件jbox使用iframe关闭问题
2009/02/09 Javascript
往光标所在位置插入值的js代码
2013/09/22 Javascript
JS保存和删除cookie操作 判断cookie是否存在
2013/11/13 Javascript
javascript操作excel生成报表示例
2014/05/08 Javascript
Javascript中的Prototype到底是什么
2016/02/16 Javascript
常用原生JS兼容性写法汇总
2016/04/27 Javascript
Vue.js学习笔记之 helloworld
2016/08/14 Javascript
详解vue的数据binding绑定原理
2017/04/12 Javascript
JavaScript禁止微信浏览器下拉回弹效果
2017/05/16 Javascript
关于前后端json数据的发送与接收详解
2017/07/30 Javascript
微信小程序实现人脸识别
2018/05/25 Javascript
JS 验证码功能的三种实现方式
2018/11/26 Javascript
uni-app微信小程序登录授权的实现
2020/05/22 Javascript
解决antd datepicker 获取时间默认少8个小时的问题
2020/10/29 Javascript
JavaScript语法约定和程序调试原理解析
2020/11/03 Javascript
[36:19]2018DOTA2亚洲邀请赛 小组赛 A组加赛 Newbee vs LGD
2018/04/03 DOTA
pandas数据框,统计某列数据对应的个数方法
2018/04/11 Python
Python爬虫之网页图片抓取的方法
2018/07/16 Python
python3+PyQt5 自定义窗口部件--使用窗口部件样式表的方法
2019/06/26 Python
用python拟合等角螺线的实现示例
2019/12/27 Python
利用python绘制正态分布曲线
2021/01/04 Python
解决pytorch 保存模型遇到的问题
2021/03/03 Python
Sunglasses Shop荷兰站:英国最大的太阳镜独立在线零售商和供应商
2017/01/08 全球购物
DC Shoes荷兰官方网站:美国极限运动品牌
2019/10/22 全球购物
波兰品牌鞋履在线商店:Eastend.pl
2020/01/11 全球购物
函授毕业生的自我鉴定
2013/11/26 职场文书
2014年党员公开承诺践诺书
2014/03/25 职场文书
文明礼貌演讲稿
2014/05/12 职场文书
技校毕业生自荐书
2014/05/23 职场文书
2014年乡镇工作总结
2014/11/21 职场文书
2016大学生暑期社会实践心得体会
2016/01/14 职场文书
导游词之寿县报恩寺
2020/01/19 职场文书
在pyCharm中下载第三方库的方法
2021/04/18 Python
一篇文章带你复习java知识点
2021/06/28 Java/Android