This post was updated 738 days ago and some of the ideas may be out of date.
利用Redis BitMap数据类型实现用户签到功能
<?php
/**
* Created by PhpStorm.
* User: LinFei
* Created time 2022/11/20 20:25:22
* E-mail: fly@eyabc.cn
*/
declare (strict_types=1);
$client = new Redis();
$client->connect('127.0.0.1', 16379);
if (!$client->ping()) {
echo 'Redis connect fail';
exit;
}
// 获取本月第几天
// $day = (int)date('d');
$day = 13;
// 用户签到数据
$userSigns = [
// 用户ID 签到天数
['id' => 1, 'sign' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]],
['id' => 2, 'sign' => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13]],
['id' => 3, 'sign' => [1, 2, 7, 8, 9, 10, 13]],
['id' => 4, 'sign' => [9, 10, 11]],
];
// 导入签到数据到Redis
foreach ($userSigns as $userSign) {
$key = 'user:sign:' . $userSign['id'];
foreach ($userSign['sign'] as $signDay) {
$client->setBit($key, $signDay, true);
}
}
/**
* 获取用户连续签到天数
* @access public
* @param int $userId 用户ID
* @param int $day 本月第几天
* @return int
* @throws RedisException
*/
function getContinuitySignDays(int $userId, int $day): int
{
global $client;
$key = 'user:sign:' . $userId;
// 获取用户签到数据
$data = $client->eval("return redis.call('BITFIELD', ARGV[1], 'GET', 'u' .. ARGV[2], 0)", [$key, $day + 1])[0] ?? 0;
// 未签到校验
if ($data === 0) {
return 0;
}
// 转换为二进制
$data = decbin($data);
// 获取连续签到天数
$continuitySignDays = 0;
// 从最后位开始往前推,直到遇到0则停止,例如:1010101101111,连续签到天数为4
for ($i = strlen($data) - 1; $i >= 0; $i--) {
if ($data[$i] === '1') {
$continuitySignDays++;
} else {
break;
}
}
return $continuitySignDays;
}
var_dump('用户1连续签到天数:' . getContinuitySignDays(1, $day) . '天,总签到天数:' . $client->bitCount('user:sign:1') . '天');
var_dump('用户2连续签到天数:' . getContinuitySignDays(2, $day) . '天,总签到天数:' . $client->bitCount('user:sign:2') . '天');
var_dump('用户3连续签到天数:' . getContinuitySignDays(3, $day) . '天,总签到天数:' . $client->bitCount('user:sign:3') . '天');
参与讨论