redis签到统计功能

基础

  • redis基础知识
  • redis 位图(getbit,setbit,bitcount,bitpos,bitop,bitfield
  • redis指令地址
<?php


namespace App\Helpers\SignIn;


use Hyperf\Config\Config;
use Hyperf\Utils\ApplicationContext;

/**
 * 签到功能(基于redis)
 * Class SignIn
 * @package App\Helpers\SignIn
 */
class SignIn
{
    /**
     * @var \Hyperf\Redis\Redis|mixed
     */
    private $redis;

    /**
     * key
     * @var
     */
    private $key;

    /**
     * 构造
     * SignIn constructor.
     */
    public function __construct()
    {

        $container = ApplicationContext::getContainer();
        $this->redis = $container->get(\Hyperf\Redis\Redis::class);
    }

    /**
     * 初始化键值
     * @param string $fix 前缀
     * @param string $tag 标识
     * @param int $type 类型 1天2月
     * @return $this
     * @throws \Exception
     */
    public function initKey(string $fix, string $tag, int $type = 1)
    {
        $format = $this->getKeyString($fix, $tag, $type);
        $this->key = $format;
        return $this;
    }

    /**
     *  键值获取格式
     * @param string $fix 前缀
     * @param string $tag 标识
     * @param int $type 类型 1天2月
     * @param int $dateInt 时间(时间戳)
     * @return string
     * @throws \Exception
     */
    public function getKeyString(string $fix, string $tag = "", int $type = 1, $dateInt = 0)
    {
        if ($dateInt == 0) {
            $dateInt = time();
        }
        $format = $fix . ':' . $tag . '-';
        switch ($type) {
            case 1:
                $format = $format . date('Ymd', $dateInt);
                break;
            case  2:
                $format = $format . date('Ym', $dateInt);
                break;
            default:
                throw new \Exception('类型异常');
        }
        return $format;
    }

    /**
     * 获取某个月份的天数
     * @param int $dateInt
     * @return false|string
     */
    protected function getMonthDate(int $dateInt = 0)
    {
        if ($dateInt == 0) {
            $dateInt = time();
        }
        return date('t', $dateInt);
    }

    /**
     *  判断是否是当前这个月
     * @param int $dateInt
     * @return bool
     */
    public function isCurrentMonth(int $dateInt = 0)
    {
        if ($dateInt == 0) {
            $dateInt = time();
        }
        $dataBeginStr = date('Y-m');
        $dataBeginInt = strtotime($dataBeginStr);
        $dataEndInt = strtotime("+1 months", $dataBeginInt);
        if ($dateInt >= $dataBeginInt && $dateInt < $dataEndInt) {
            return true;
        }
        return false;
    }

    /**
     * 设置键值
     * @param string $key
     * @return $this
     */
    public function setKey(string $key)
    {
        $this->key = $key;
        return $this;
    }

    /**
     * 设置某人签到(一天内包含所有人)
     * @param $userId
     * @param int $isLogin 1签到 0撤销签到
     * @param string $signKey 特定键值
     * @throws \Exception
     */
    public function signDayEveryOne($userId, $isLogin, $signKey = '')
    {
        if (empty($signKey)) {
            $signKey = $this->getKeyString("Login");
        }
        $userId = intval($userId);
        $num = $this->redis->setBit($signKey, $userId, $isLogin == 1);
        if ($num < 0) {
            throw new \Exception('签到失败');
        }
    }

    /**
     * 设置每个人一个月内每一天的的的签到情况
     * @param $userId
     * @param int $isLogin 1签到 0撤销签到
     * @param string $date 签到日期2020-02-01
     * @throws \Exception
     */
    public function signOneEveryDay($userId, int $isLogin, string $date = '')
    {
        if (empty($date) || !strtotime($date)) {
            $dateInt = 0;
            $day = date('d');
        } else {
            $dateInt = strtotime($date);
            $day = date('d', $dateInt);
        }
        if (empty($signKey)) {
            $signKey = $this->getKeyString("Login", $userId, 2, $dateInt);
        }
        // 位数的第一个是从0开始的
        $day = $day - 1;
        var_dump($day);
        $num = $this->redis->setBit($signKey, $day, $isLogin == 1);
        if ($num < 0) {
            throw new \Exception('签到失败');
        }
    }

    /**
     * 统计不同key中的与或非关系(主要用户统计连续几天不同人的签到情况)
     * @param array $keysArr key
     * @param bool $isReg 是否模糊匹配
     * @param string $operation "AND", "OR", "NOT", "XOR"
     * @param bool $isCache 是否取缓存数据
     * @return int 个数
     * @throws \Exception
     */
    public function getdayCount(array $keysArr, $isReg = false, $operation = "AND", $isCache = false)
    {
        $countKey = 'Login:' . md5(implode(',', $keysArr) . $operation);
        if ($isCache) {
            //  查找缓存中的数据
            $countNum = $this->redis->bitCount($countKey);
            if ($countNum > 0) {
                return $countNum;
            }
        }
        if ($isReg) {
            $keysArr = $this->redis->keys($keysArr[0]);
        }
        if (empty($keysArr)) {
            throw new \Exception('不存在key');
        }
        array_unshift($keysArr, $countKey);
        array_unshift($keysArr, $operation);
        //用于传递不定变量
        call_user_func_array([$this->redis, 'bitOp'], $keysArr);
        // 获取统计
        $countNum = $this->redis->bitCount($countKey);
        //设置有效时间
        $this->redis->expire($countKey, 20);
        return $countNum;
    }

    /**
     * 获取所有模糊key
     * @param $key
     * @return array
     */
    protected function getKeys($key)
    {
        $cursor = null;
        $this->redis->setOption(\Redis::OPT_SCAN, \Redis::SCAN_RETRY);
        $arr = [];
        while ($res = $this->redis->scan($cursor, $key, 20)) {
            if (is_array($res)) {
                $arr = array_merge($arr, $res);
            }
        }
        return $arr;
    }

    /**
     * 获取用户的 一个月第一天签到时间
     * @param int $userId 用户id
     * @param string $date 日期
     * @return int 0 表示当前月未签到
     * @throws \Exception
     */
    public function getFirstSignDate(int $userId, string $date)
    {
        $dayNum = 0;
        $signKey = $this->getKeyString("Login", $userId, 2, strtotime($date));
        $num = $this->redis->bitpos($signKey, 1);
        if ($num >= 0) {
            $dayNum = $num + 1;
        }
        return $dayNum;
    }

    /**
     * 获取某个月签到的所有
     * @param int $userId
     * @param string $date
     * @return array|int
     * @throws \Exception
     */
    public function getMonthSignDetail(int $userId, string $date)
    {
        $dateInt = strtotime($date);
        //获取这个月第一次签到时间
        $firstSignDate = $this->getFirstSignDate($userId, $date);
        if ($firstSignDate == 0) {
            return 0;
        }
        // 获取某个月份的天数
        $monthDateNum = $this->getMonthDate($dateInt);
        // 获取key值
        $signKey = $this->getKeyString("Login", $userId, 2, $dateInt);
        // 获取这个月的签到情况 (10进制数 位数超过的天数用0补位,从第一位开始取,取$monthDateNum位,如果取$monthDateNum位大于63,需要 分次取否则报错 )
        $list = $this->redis->rawCommand('BITFIELD', $signKey, 'GET', 'u' . $monthDateNum, 0);
        $num = $list[0];
        // 转化成二级制 (位数超过的天数用0补位 所以要去除无效0,从左到右的,左边头上的0已经去除所以之后需要补位 )
        $decbinStr = decbin($num);
        //字符串转化成数组
        $decbinStrArr = str_split($decbinStr);
        // 数组反转
        $decbinStrArr2 = array_reverse($decbinStrArr);
        //数组头部等于0 的位数
        $loginNum = 0;
        foreach ($decbinStrArr2 as $vo) {
            if ($vo > 0) {
                break;
            }
            $loginNum = $loginNum + 1;
        }
        // 数组去除头部0
        array_splice($decbinStrArr2, 0, $loginNum);
        // 通过第一次签的位数,来确定之前有几天没有签到,补上0
        for ($i = 1; $i < $firstSignDate; $i++) {
            array_push($decbinStrArr2, "0");
        }
        // 判断时间是否在这个月内的
        $isCurrentMonth = $this->isCurrentMonth($dateInt);
        if ($isCurrentMonth) {
            //当前这个月有效天数
            $monthDateNum = intval(date('d'));
        }
        //签到数据如果少于 实际天数 那么在头追加没有签到个数 0
        $differenceNum = $monthDateNum - count($decbinStrArr2);
        if ($differenceNum > 0) {
            for ($n = 0; $n < $differenceNum; $n++) {
                array_unshift($decbinStrArr2, '0');
            }
        }
        //数组反转
        $decbinStrArr3 = array_reverse($decbinStrArr2);
        return [
            'left'  => $decbinStrArr3, // 从左向有看 升位
            'right' => $decbinStrArr2, // 从右向左看 升位
        ];
    }

    /**
     * 获取连续签到次数
     * @param int $userId
     * @param string $date
     * @return int
     * @throws \Exception
     */
    public function getMonthContinuousSignNum(int $userId, string $date)
    {
        $MonthSignDetail = $this->getMonthSignDetail($userId, $date);
        $decbinStrArr = $MonthSignDetail['left'];
        $num = count($decbinStrArr);
        $continuousNum = 0;
        for ($i = 0; $i < $num; $i++) {
            if ($i == 0) {
                if ($decbinStrArr[0] == 1) {
                    $continuousNum = 1;
                }
            } else {
                if ($decbinStrArr[$i] == 1) {
                    $continuousNum = $continuousNum + 1;
                } else {
                    $continuousNum = 0;
                }
            }
        }
        return $continuousNum;
    }


}

版权声明:本文为miaocansky原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。