基础
- 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版权协议,转载请附上原文出处链接和本声明。