公共方法
构建健
设置redis的key,例如用户A获取2021-03的key返回user:sign:A:202103
private function buildSignKey($user, $date = '')
{
return "user:sign:" . $user . ':' . $this->buildDate($date);
}
构建日期格式
将日期转换成指定的日期格式
- 当$data=10说明位时间戳格式,直接转换成指定的日期格式
- 否则将日期格式转换成时间戳后转成指定格式
private function buildDate($date = '', $conversion = 'Ym')
{
if (empty($date)) {
return '';
}
if (strlen($date) == 10 && is_numeric($date)) {
return date($conversion, $date);
}
return date($conversion, strtotime($date));
}
构建日期位
打卡记录的位置位日期-1.例如03-01的在bitmap中的偏移量位1
private function getDateOffset($date)
{
return intval(date("d", strtotime($date))) - 1;
}
流转位图
通过get获取bitmap的流文件,将留文件转换成位图,示例: 1000001000.
private function dealBinary($bitmap_str)
{
// 对数据流使用网络字节序(大端)解包拿到16进制数据的字符串形式
$hex_str = unpack("H*", $bitmap_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;
}
功能
签到
public function sign()
{
$redis = RedisBase::getInstance();
$date = !empty($this->params['date']) ? $this->params['date'] : date("Y-m-d");
$offset = $this->getDateOffset($date);
$signKey = $this->buildSignKey($this->user, $date);
// 第一次设置值返回0,在设置返回1
$res = $redis->setBit($signKey, $offset, true);
if ($res) {
return $this->error('不可重复签到');
}
return $this->success(['date' => $date], '签到成功');
}
验证签到
public function checkSign()
{
$redis = RedisBase::getInstance();
$date = !empty($this->params['date']) ? $this->params['date'] : date("Y-m-d");
$offset = $this->getDateOffset($date);
$signKey = $this->buildSignKey($this->user, $date);
$res = $redis->getBit($signKey, $offset);
if (empty($res)) {
return $this->success(['status' => false]);
}
return $this->success(['status' => true]);
}
获取月签到次数
public function getSignNumByMonth()
{
$redis = RedisBase::getInstance();
$date = !empty($this->params['date']) ? $this->params['date'] : date("Y-m-d");
$signKey = $this->buildSignKey($this->user, $date);
$count = $redis->bitCount($signKey);
return $this->success([
'date' => $this->buildDate($date),
'count' => $count
]);
}
获取签到日历
public function getSignContinuousNumByMonth()
{
$redis = RedisBase::getInstance();
$date = !empty($this->params['date']) ? $this->params['date'] : date("Y-m-d");
$signKey = $this->buildSignKey($this->user, $date);
// 计算该月有多少天
$days = cal_days_in_month(CAL_GREGORIAN, date('m', strtotime($date)), date('Y', strtotime($date)));
$stream = $redis->get($signKey);
$cardInfo = $this->dealBinary($stream);
$res = [];
$month = substr($date, 0, 8);// 截取年-月- 例:2021-03-
for ($i = 0; $i < $days; $i++) {
$day = $i + 1;
if ($day < 10) { // 当日期小于10用0补齐
$day = '0' . $day;
}
$arrayDate = $month . $day;
$res[$arrayDate] = 0;
if ($cardInfo{$i} == 1) {
$res[$arrayDate] = 1;
}
}
return $this->success($res);
}
返回结果
{
"code": 0,
"msg": "ok",
"data": {
"2021-03-01": 0,
"2021-03-02": 0,
"2021-03-03": 0,
"2021-03-04": 0,
"2021-03-05": 0,
"2021-03-06": 0,
"2021-03-07": 0,
"2021-03-08": 0,
"2021-03-09": 1, // 已签到
"2021-03-10": 0,
"2021-03-11": 0,
"2021-03-12": 0,
"2021-03-13": 0,
"2021-03-14": 0,
"2021-03-15": 0,
"2021-03-16": 0,
"2021-03-17": 0,
"2021-03-18": 0,
"2021-03-19": 0,
"2021-03-20": 0,
"2021-03-21": 0,
"2021-03-22": 0,
"2021-03-23": 0,
"2021-03-24": 0,
"2021-03-25": 0,
"2021-03-26": 0,
"2021-03-27": 0,
"2021-03-28": 0,
"2021-03-29": 0,
"2021-03-30": 0,
"2021-03-31": 0
}
}
牛逼