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 相关文章推荐
cache_lite试用
Feb 14 PHP
php连接mssql的一些相关经验及注意事项
Feb 05 PHP
基于PHP CURL用法的深入分析
Jun 09 PHP
PHP_Cooikes不同页面无法传递的解决方法
Mar 07 PHP
PHP中copy on write写时复制机制介绍
May 13 PHP
php字符比较函数similar_text、strnatcmp与strcasecmp用法分析
Nov 18 PHP
PHP进程同步代码实例
Feb 12 PHP
PHP中Session和Cookie是如何操作的
Oct 10 PHP
php好代码风格的阶段性总结
Jun 25 PHP
PHPStrom 新建FTP项目以及在线操作教程
Oct 16 PHP
php 浮点数比较方法详解
May 05 PHP
php实现的生成迷宫与迷宫寻址算法完整实例
Nov 06 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中设置、使用、删除Cookie的解决方法
2013/05/06 PHP
基于php导出到Excel或CSV的详解(附utf8、gbk 编码转换)
2013/06/25 PHP
php 如何获取数组第一个值
2013/08/06 PHP
从零开始学YII2框架(三)扩展插件yii2-gird
2014/08/20 PHP
自定义session存储机制避免会话保持问题
2014/10/08 PHP
php metaphone()函数及php localeconv() 函数实例解析
2016/05/15 PHP
jquery 根据name名获取元素的value值
2015/02/27 Javascript
jQuery Validation PlugIn的使用方法详解
2015/12/18 Javascript
Backbone.js框架中Model与Collection的使用实例
2016/05/07 Javascript
jQuery插件实现文件上传功能(支持拖拽)
2020/08/27 Javascript
浅谈js的html元素的父节点,子节点
2016/08/06 Javascript
实例解析jQuery中如何取消后续执行内容
2016/12/01 Javascript
微信小程序 省市区选择器实例详解(附源码下载)
2017/01/05 Javascript
详解Node.js串行化流程控制
2017/05/04 Javascript
详解vuex 中的 state 在组件中如何监听
2017/05/23 Javascript
angularjs定时任务的设置与清除示例
2017/06/02 Javascript
jquery操作ul的一些操作笔记整理(干货)
2017/08/31 jQuery
轻松玩转BootstrapTable(后端使用SpringMVC+Hibernate)
2017/09/06 Javascript
vue2.0 和 animate.css的结合使用
2017/12/12 Javascript
利用JS判断客户端类型你应该知道的四种方法
2017/12/22 Javascript
Linux Centos7.2下安装nodejs&amp;npm配置全局路径的教程
2018/05/15 NodeJs
vue和webpack项目构建过程常用的npm命令详解
2018/06/15 Javascript
jquery实现自定义树形表格的方法【自定义树形结构table】
2019/07/12 jQuery
忘记ftp密码使用python ftplib库暴力破解密码的方法示例
2014/01/22 Python
Jupyter notebook无法导入第三方模块的解决方式
2020/04/15 Python
CSS3实现菜单悬停效果
2020/11/17 HTML / CSS
英国家庭珠宝商:T. H. Baker
2018/02/08 全球购物
工程预算与管理应届生求职信
2013/10/06 职场文书
上班早退检讨书
2014/01/09 职场文书
2014政务公开实施方案
2014/02/19 职场文书
愚人节活动策划方案
2014/03/11 职场文书
大一新生期末自我评价
2014/09/12 职场文书
优秀党员主要事迹范文
2015/11/05 职场文书
创业计划书之冷饮店
2019/09/27 职场文书
简单聊聊Golang中defer预计算参数
2022/03/25 Golang
mysql5.5中文乱码问题解决的有用方法
2022/05/30 MySQL