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中使用gettext来支持多语言的方法
May 02 PHP
php强制运行广告的方法
Dec 01 PHP
PHP获取QQ达人QQ信息的方法
Mar 05 PHP
PHP代码实现表单数据验证类
Jul 28 PHP
PHP使用http_build_query()构造URL字符串的方法
Apr 02 PHP
php版微信自动获取收货地址api用法示例
Sep 22 PHP
thinkPHP交易详情查询功能详解
Dec 02 PHP
php生成图片缩略图功能示例
Feb 22 PHP
ThinkPHP模板标签eq if 中区分0,null,false的方法
Mar 24 PHP
基于Laravel5.4实现多字段登录功能方法示例
Aug 11 PHP
PHP回调函数简单用法示例
May 08 PHP
PHP8.0新功能之Match表达式的使用
Jul 19 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
总集篇&特番节目先行播出!《SAO Alicization War of Underworld》第2季度TV动画4月25日放送!
2020/03/06 日漫
在同一窗体中使用PHP来处理多个提交任务
2006/10/09 PHP
PHP的简易冒泡法代码分享
2012/08/28 PHP
PHP中把有符号整型转换为无符号整型方法
2015/05/27 PHP
分享PHP守护进程类
2015/12/30 PHP
全面解析PHP面向对象的三大特征
2017/06/10 PHP
php 获取xml接口数据的处理方法
2018/05/31 PHP
Laravel5.4框架中视图共享数据的方法详解
2019/09/05 PHP
详解CSS样式中的 !important * _ 符号
2021/03/09 HTML / CSS
javascript 检测浏览器类型和版本的代码
2009/09/15 Javascript
javascript返回顶部效果(自写代码)
2013/01/06 Javascript
仿谷歌主页js动画效果实现代码
2013/07/14 Javascript
js打开新窗口方法整理
2014/02/17 Javascript
nodejs开发微博实例
2015/03/25 NodeJs
AngularJS过滤器详解及示例代码
2016/08/16 Javascript
jQuery.Form上传文件操作
2017/02/05 Javascript
JavaScript中click和onclick本质区别与用法分析
2018/06/07 Javascript
vue 组件开发原理与实现方法详解
2019/11/29 Javascript
python以环状形式组合排列图片并输出的方法
2015/03/17 Python
Python爬虫获取图片并下载保存至本地的实例
2018/06/01 Python
python的scikit-learn将特征转成one-hot特征的方法
2018/07/10 Python
python 通过麦克风录音 生成wav文件的方法
2019/01/09 Python
详解Python3网络爬虫(二):利用urllib.urlopen向有道翻译发送数据获得翻译结果
2019/05/07 Python
浅谈PyTorch的可重复性问题(如何使实验结果可复现)
2020/02/20 Python
django 前端页面如何实现显示前N条数据
2020/03/16 Python
解决IDEA 的 plugins 搜不到任何的插件问题
2020/05/04 Python
matplotlib更改窗口图标的方法示例
2021/02/03 Python
英国皇家造币厂:The Royal Mint
2018/10/05 全球购物
Interrail法国:乘火车探索欧洲,最受欢迎的欧洲铁路通票
2019/08/27 全球购物
英国排名第一的LED灯泡网站:LED Bulbs
2019/09/03 全球购物
进程的查看和调度分别使用什么命令
2013/12/14 面试题
畜牧兽医本科生的自我评价
2014/03/03 职场文书
竞聘书模板
2014/03/31 职场文书
励志演讲稿200字
2014/08/21 职场文书
古诗文之爱国名句(77句)
2019/09/24 职场文书
pandas进行数据输入和输出的方法详解
2022/03/23 Python