大家好,我是正文
本章简单跟大家讲一下PHP使用redisBitmap做一个签到功能
首先,我们要理解什么是Bitmap类型 理解了以后才方便理解下面的内容
首先是reids存储bitmap的方式
setbit key offset val
gitbit key offset
常用的两种命令
可以看得出,他是以key + 偏移量 + 值进行存储的
我们可以用二进制去理解他 如 0000010010011111111 这样的
一般这样的一串是一个key所代表的内容 而里面的下标 也就不言而喻的 每一个单位值
bitmap存储的值都是使用1/0来进行储存
所以 套入到签到功能 当前的用户:年月 作为key 而 day 可以作为offset
直接在PHP进行获取并使用即可
签到
1、判断当前日是否被签到
2、判断前一天我们是否有签到过
3、查询mysql中是否存在签到记录
若没有签到记录 则说明是首次签到
setbit签到后
我们要先使用模型添加一条记录 同时初始化连签次数和积分赠送
这个时候 首次签到就已经完成了
而连签的逻辑在于
1、不是首次签到
2、签到前一天有成功签到的记录
我们可以正常跑逻辑了
首先前一天已经签到了 一般来讲积分赠送都是递归或者根据需求进行积分的发放
所以我们可以使用数据库中的连签次数*给定的积分 + 每日独立的签到积分奖励
然后将连签次数+1
添加完成后 可以进行数据库积分的发放 或者是 奖励方法
如积分奖励的计算 或是 优惠劵发放等
若不是连签 也就是断签进行签到
我们可以直接进行签到的定义 然后将数据库中你的连签次数转为1 积分奖励依然是一样的
<?php
namespace App\Http\Controllers\api;
use App\Busincess\Common\CommonBusincess;
use App\Busincess\Common\Json;
use App\Busincess\Logic\SignBusincess;
use App\Http\Controllers\Controller;
use App\Models\Coupon;
use App\Models\Sign;
class SignController extends Controller
{
protected static $redis;
public function __construct()
{
self::$redis = new \Redis();
self::$redis->connect('127.0.0.1', 6379);
}
/**
* Created by PhpStorm
* Name:signIn
* User: 立早正文
* DateTime: 2022/11/13 13:14
* Desc:发起签到
*/
public function actionUserSignIn()
{
$uid = \request()->uid;
//查看今天的签到状态
$date = date('Y-m', time());
$day = date('d', time());
$key = $uid . ":" . $date;
//获取指定bitmap列 中的下标 得到当前下标到状态
$nowSignStatus = self::$redis->getBit($key, $day);
if ($nowSignStatus) return Json::error(config('code.Error'), '您已经签到过了');
//查看昨天的签到状态
$yesterday = date('d', strtotime('-1 day'));
$yesterdaySignStatus = self::$redis->getBit($key, $yesterday);
$signInfo = Sign::findUid($uid);
//第一次签到
if (empty($signInfo)) {
$params = [
'uid' => $uid,
'sign_num' => 1,
'is_backup' => 0,
'integral' => 10
];
$result = Sign::create($params);
self::$redis->setBit($key, $day, 1);
return Json::succeed(config('bingo'), '恭喜您首次签到成功');
}
//不是首次签到 判断昨日是否签到
if ($yesterdaySignStatus) {
//连签
$signInfo['integral'] += 10 + ($signInfo['sign_num'] * 10);
$signInfo['sign_num'] += 1;
$signInfo->save();
if ($signInfo['sign_num'] == 7) {
//7连签
$signInfo['integral'] += 50;
$signInfo->save();
return Json::succeed(config('bingo'), '恭喜您连续签到成功,已向您赠送50积分');
}
if ($signInfo['sign_num'] == 14) {
//14连签
$params = [
'uid' => $uid,
'name' => "满50 - 5 优惠劵",
'min_price' => 50,
'minus_price' => 5
];
Coupon::create($params);
return Json::succeed(config('bingo'), '恭喜您连续签到14天,已向您的账户发放了满50 - 5 优惠劵');
}
if ($signInfo['sign_num'] == 30) {
//月连签
$params = [
'uid' => $uid,
'name' => "无门槛5000000元优惠劵",
'min_price' => 0,
'minus_price' => 5
];
Coupon::create($params);
return Json::succeed(config('bingo'), '恭喜您连续签到31天,已向您的账户发放了无门槛5000000元优惠劵');
}
self::$redis->setBit($key, $day, 1);
return Json::succeed(config('bingo'), '恭喜您连续签到成功');
} else {
//断签
$signInfo['sign_num'] = 1;
$signInfo['integral'] += 10;
$signInfo->save();
self::$redis->setBit($key, $day, 1);
return Json::succeed(config('bingo'), '恭喜您重新签到成功');
}
}
/**
* Created by PhpStorm
* Name:getMonthSign
* User: 立早正文
* DateTime: 2022/11/13 17:35
* Desc:当月签到情况
*/
public function getMonthSign()
{
try {
$uid = \request()->uid;
$date = date('Y-m', time());
//获取用户签到信息
$key = $uid . ':' . $date;
$sign = self::$redis->get($key);
//处理数据
$bitmap_bin_str = CommonBusincess::strToBin($sign);
return Json::succeed(config('code.bingo'), '本月签到详情', $bitmap_bin_str);
} catch (\Exception $e) {
return Json::error(config('code.Error'), $e->getMessage());
}
}
```
首先获取到用户要进行补签的日期
判断补签是佛合法 比如重复补签 、日期是否正常
判断后正常进行补签
记得扣除指定积分后再签到
有三个时间比较重要
补签日 前一天 当前日期
```
/**
* Created by PhpStorm
* Name:actionRepair
* User: 立早正文
* DateTime: 2022/11/13 16:47
* Desc:补签
*/
public function actionRepair()
{
try {
//uid
$uid = \request()->uid;
//补签具体的哪一天
$day = \request()->post('day');
//当前月
$date = date('Y-m', time());
//比较补签时间与当前时间
if (strtotime($date . '-' . $day) >= strtotime(date('Y-m-d', time()))) return Json::error(config('code.Error'), '补签异常');
//补签当月的bit $key
$key = $uid . ":" . $date;
//今天
$thisDay = date('d', time());
//获取补签天的签到状态
$signStatus = self::$redis->getBit($key, $day);
if ($signStatus) return Json::error(config('code.Error'), '该日已签到');
//消耗30积分补签
$signInfo = Sign::findUid($uid);
$signInfo->integral -= 30;
$signInfo->save();
//发起补签
self::$redis->setBit($key, $day, 1);
$lastDay = date('d',strtotime('-1 day'));
$lastDaySatatus = self::$redis->getBit($key,$lastDay);
$i = 1 ;
if($lastDaySatatus)
{
//查询前一天的用户连签次数
SignBusincess::getNum($uid,$date,$lastDay,$i);
//查询今日签到状态
$thisDaySignStatus = self::$redis->getBit($key,$thisDay);
//若签到
if($thisDaySignStatus)
{
//补签前的连签次数+1
$signInfo = Sign::findUid($uid);
$signInfo->sign_num = $i + 1;
$signInfo->save();
return Json::succeed(config('code.bingo'),'补签成功,恭喜您现在连续补签'.$signInfo->sign_num."天");
}
//若今日没签 载入补签后的连签次数
$signInfo = Sign::findUid($uid);
$signInfo->sign_num = $i;
$signInfo->save();
return Json::succeed(config('code.bingo'),'补签成功,恭喜您现在连续补签'.$signInfo->sign_num."天");
}
//
Sign::findUid($uid);
$signInfo->sign_num = 1;
$signInfo->save();
return Json::succeed(config('code.bingo'),'补签成功,恭喜您现在连续补签'.$signInfo->sign_num."天");
} catch (\Exception $e) {
return Json::error(config('code.Error'), $e->getMessage());
}
}
}
/**
* 获取用户连续签到天数
* @param $uid
* @param $date
* @param $day
* @param $i
*/
public static function getNum($uid, $date, $day, &$i)
{
try {
$key = $uid . ':' . $date;
$sign = self::$redis->getBit($key, $day);
if ($sign == 1) {
$i++;
if ($day - 1 > 0) {
self::getNum($uid, $date, $day - 1, $i);
} else {
$time = strtotime($date . '0' . $day) - 86400;
$mytime = date('Ymd', $time);
$day = substr($mytime, 6, 2);
$month = substr($mytime, 0, 6);
self::getNum($uid, $month, $day, $i);
}
}
} catch (\Exception $e) {
return Json::error(500, $e->getMessage());
}
}
/**
* 数据转换为数字
* @param $str
* @return string
*/
public static function strToBin($str)
{
// 对数据流使用网络字节序(大端)解包拿到16进制数据的字符串形式
$hex_str = unpack("H*", $str)[1];
// hex str 的长度
$hex_str_len = strlen($hex_str);
// 为了防止 hex to dec 时发生溢出
// 我们需要切分 hex str,使得每一份 hex str to dec 时都能落在 int 类型的范围内
// 因为 2 位 16 进制表示一个字节,所以用系统 int 类型的字节长度去分组是绝对安全的
$chunk_size = PHP_INT_SIZE;
// 对 hex str 做分组对齐,否则 str 的最后几位可能会被当作低位数据处理
// 比如 fffff 以 4 位拆分 'ffff', 'f' 后 最后一组 'f' 就被低位数据处理了
// 对齐后 fffff000 分组 'ffff', 'f000' 就能保证 'f' 的数据位了
$hex_str = str_pad($hex_str, $hex_str_len + ($chunk_size - ($hex_str_len % $chunk_size)), 0, STR_PAD_RIGHT);
// 防止 hexdec 时溢出 使用 PHP_INT_SIZE 个 16 进制字符一组做拆分
// 因 16 进制 2 位标识一个字节 所以 PHP_INT_SIZE 是绝对不会溢出的
$hex_str_arr = str_split($hex_str, $chunk_size);
// 位数据的二进制字符串
$bitmap_bin_str = '';
array_walk($hex_str_arr, function ($hex_str_chunk) use (&$bitmap_bin_str, $chunk_size) {
$bitmap_bin_str .= str_pad(decbin(hexdec($hex_str_chunk)), $chunk_size * 4, 0, STR_PAD_LEFT);
});
return $bitmap_bin_str;
}