This post was updated 585 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') . '天');