侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

laravel框架 + redis + bitmap实现签到功能示例代码

Laravel  /  管理员 发布于 3年前   1038

bitmap一种基于位的映射,bitmap是一个十分有用的结构。

所谓的Bit-map就是用一个bit位来标记某个元素对应的Value, 而Key即是该元素。

由于采用了Bit为单位来存储数据,因此可以大大节省存储空间


BitMap思想:

一个byte是占8个bit,如果每一个bit的值就是有或者没有,也就是二进制的0或者1,

如果用bit的位置代表数组值有还是没有,那么0代表该数值没有出现过,1代表该数组值出现过。


示例代码:

签到服务

<?php
namespace App\Services;
use App\Events\UserSignedIn;
use App\Models\User;
use App\Models\UserSignIn;
use Illuminate\Redis\Connections\Connection as RedisConnection;
use Illuminate\Redis\Connections\PhpRedisConnection;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Redis;
class SignInService extends Service
{
    protected PhpRedisConnection | RedisConnection $redis;
    public function __construct($redis = null)
    {
        $this->redis = $redis ?? Redis::connection('sign_in');
    }
    public function signIn(User $user, Carbon $date): bool
    {
        $key = $this->buildKey($user, $date);
        $offset = $date->day - 1;
        $signedIn = (bool)$this->redis->getBit($key, $offset);
        if (!$signedIn) {
            $this->redis->setBit($key, $offset, 1);
            $continuousDays = $this->getMonthsContinuousSignCount($user, $date);
            $periodsDays = $this->getMonthContinuousSignCount($user, $date);
            $signIn = UserSignIn::create([
                'user_id' => $user->id,
                'continuous_days' => $continuousDays,
                'periods_days' => $periodsDays,
                'date' => $date->toDateString(),
            ]);
            UserSignedIn::dispatch($signIn);
        }
        return true;
    }
    /**
     * 获取指定日期是否有签到
     * @param User $user
     * @param Carbon $date
     * @return bool
     */
    public function hasSigned(User $user, Carbon $date): bool
    {
        $key = $this->buildKey($user, $date);
        $offset = $date->day - 1;
        return (bool)$this->redis->getBit($key, $offset);
    }
    /**
     * 获取连续签到天数
     * @param User $user
     * @param Carbon $endDate
     * @param Carbon|null $startDate
     * @return int
     */
    public function getContinuousSignCount(User $user, Carbon $endDate, ?Carbon $startDate = null): int
    {
        $startDate ??= $endDate->copy()->startOfMonth();
        [$startDate, $endDate] = [$startDate->copy()->startOfDay(), $endDate->copy()->startOfDay()];
        $currentDate = $endDate->copy();
        $totalSignCount = 0;
        // endDate -> startDate
        while ($currentDate->gte($startDate)) {
            $key = $this->buildKey($user, $currentDate);
            $_startDate = $startDate->max($currentDate->copy()->startOfMonth());
            $days = $_startDate->diffInDays($currentDate) + 1;
            $offset = $_startDate->day - 1;
            $count = $this->getBitField($key, $days, $offset);
            $signCount = 0;
            while ($count & 1) {
                $signCount++;
                $count >>= 1;
            }
            $totalSignCount += $signCount;
            if ($signCount < $days) {
                break;
            }
            $currentDate->startOfMonth()->subDay();
        }
        return $totalSignCount;
    }
    /**
     * 获取跨月连续签到次数
     * @param User $user
     * @param Carbon $endDate 截止日期
     * @return int
     */
    public function getMonthsContinuousSignCount(User $user, Carbon $endDate): int
    {
        $signCount = $this->getMonthContinuousSignCount($user, $endDate);
        if ($signCount == $endDate->day) {
            $signCount += $this->getMonthsContinuousSignCount($user, $endDate->copy()->startOfMonth()->subDay());
        }
        return $signCount;
    }
    /**
     * 获取单月连续签到次数
     * @param User $user
     * @param Carbon $endDate 截止日期
     * @return int
     */
    public function getMonthContinuousSignCount(User $user, Carbon $endDate): int
    {
        $key = $this->buildKey($user, $endDate);
        $count = $this->getBitField($key, $endDate->day);
        $signCount = 0;
        while ($count & 1) {
            $signCount++;
            $count >>= 1;
        }
        return $signCount;
    }
    /**
     * 通过bitField获取签到天数
     * @param User $user
     * @param Carbon $endDate 截止时间
     * @return int
     */
    public function getMonthSignCountByBitField(User $user, Carbon $endDate): int
    {
        $key = $this->buildKey($user, $endDate);
        $count = $this->getBitField($key, $endDate->day);
        $signCount = $count & 1;
        while ($count >>= 1) {
            $signCount++;
        }
        return $signCount;
    }
    /**
     * 获取签到天数
     * @param User $user
     * @param Carbon $date
     * @return int
     */
    public function getMonthSignCount(User $user, Carbon $date): int
    {
        $key = $this->buildKey($user, $date);
        return $this->redis->bitCount($key);
    }
    /**
     * 获取签到map
     * @param User $user
     * @param Carbon $endDate 截止日期
     * @return array
     */
    public function getMonthSignMap(User $user, Carbon $endDate): array
    {
        $key = $this->buildKey($user, $endDate);
        $count = $this->getBitField($key, $endDate->day);
        $day = $endDate->day;
        $map = array_fill(0, $day, 0);
        while ($day--) {
            $map[$day] = $count & 1;
            if (!($count >>= 1)) {
                break;
            }
        }
        return $map;
    }
    /**
     * @param string $key
     * @param int $length
     * @param ?int $offset
     * @return int
     */
    protected function getBitField(string $key, int $length, ?int $offset = 0): int
    {
        [$count] = call_user_func([$this->redis, 'eval'], <<<LUA
            return redis.call('BITFIELD', KEYS[1], 'GET', ARGV[1], ARGV[2])
        LUA, 1, $key, 'u'.$length, $offset);
        return $count ?? 0;
    }
    /**
     * 生成key,因为 BITFIELD 指令无符号获取的偏移量最大是63,所以一个key只存一个月份的数据
     * @param User $user
     * @param Carbon $date
     * @return string
     */
    protected function buildKey(User $user, Carbon $date): string
    {
        return sprintf('sign_in:%u:%u', $user->id, $date->format('Ym'));
    }
}

签名测试

<?php
namespace Tests\Feature;
use App\Events\UserSignedIn;
use App\Models\User;
use App\Services\SignInService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Redis\Connections\PhpRedisConnection;
use Tests\TestCase;
class SignInTest extends TestCase
{
    public function test_sign_in(): void
    {
        /**
         * @var PhpRedisConnection $redis
         */
        $redis = app('redis')->connection('sign_in');
        $redis->flushAll();
        $service = new SignInService($redis);
        $user = User::first();
        $now = now()->startOfMonth()->addDays(2);
        app('events')->forget(UserSignedIn::class);
        collect([...range(0, 5), ...range(7, 10), ...range(9, 15)])
            ->sortDesc()
            ->each(fn ($daysAgo) => $service->signIn($user, $now->copy()->subDays($daysAgo)));
        $this->assertTrue($service->getContinuousSignCount($user, $now) === 3);
        $this->assertTrue($service->getContinuousSignCount($user, $now, $now->copy()->subDays(3)) === 4);
        $this->assertTrue($service->getContinuousSignCount($user, $now, $now->copy()->subDay()) === 2);
        $this->assertTrue($service->getMonthsContinuousSignCount($user, $now) === 6);
        $this->assertTrue($service->getMonthContinuousSignCount($user, $now) === 3);
        $this->assertTrue(array_is_list($service->getMonthSignMap($user, $now)));
        $this->assertTrue(array_slice($service->getMonthSignMap($user, $now->copy()->addDay()), -1)[0] === 0);
    }
    public function test_sign_score()
    {
        $service = app(SignInService::class);
        $user = User::first();
        $now = now()->startOfMonth()->addDays(3);
        $service->signIn($user, $now);
        $service->signIn($user, $now);
        $service->signIn($user, $now->copy()->addDay());
        $this->assertTrue($user->score->usable_score == 65);
    }
}

转:

https://learnku.com/articles/68445

  • 上一条:
    laravel Passport中撤销、删除个人令牌的示例代码
    下一条:
    go语言中对文件读写权限的检查,更改等功能的示例代码
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • Laravel 11.15版本发布 - Eloquent Builder中添加的泛型(0个评论)
    • Laravel 11.14版本发布 - 新的字符串助手和ServeCommand改进(0个评论)
    • Laravel 11.12版本发布 - Artisan的`make`命令自动剪切`.php `扩展(0个评论)
    • Laravel的轻量型购物车扩展包:binafy/laravel-cart(0个评论)
    • Laravel 11.11版本发布 - 查看模型中的第三方关系:show(0个评论)
    • 近期文章
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • PHP 8.4 Alpha 1现已发布!(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-07
    • 2017-08
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-08
    • 2020-09
    • 2020-10
    • 2020-11
    • 2021-01
    • 2021-02
    • 2021-03
    • 2021-04
    • 2021-05
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-11
    • 2021-12
    • 2022-01
    • 2022-02
    • 2022-03
    • 2022-04
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-08
    • 2023-09
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-02
    • 2024-03
    • 2024-04
    • 2024-05
    • 2024-06
    • 2024-07
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客