需求

用户每日点击签到,获得x积分;用户连续签到y天后额外获得z积分。
x,y,z分别由后台配置。
需要记录用户今天是否已签到当前连续签到数本月签到列表数据供前端展示

表结构

CREATE TABLE `sign_in_logs` (
      `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '用户id',
      `year` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '年',
      `month` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '月',
      `date_list` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '签到日期列表(按位储存)',
      `count` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '累计连续签到的天数,到一定数量后清零',
      `last_date` varchar(16) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '上一次签到的日期',
      `created_at` timestamp NULL DEFAULT NULL,
      `updated_at` timestamp NULL DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `sign_in_logs_user_id_index` (`user_id`),
      KEY `sign_in_logs_year_month_index` (`year`,`month`),
      KEY `sign_in_logs_date_list_index` (`date_list`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

用户每次签到成功,count字段自增1,last_date记录时间,date_list按位储存本月签到数据,当count字段自增后 >= y 时,额外加z分再把count归零
重点在date_list字段,该字段以10进制储存,但在查询和插入的时候按二进制进行位运算处理。
二进制除去最高符号位一共31位刚好够存最大月份,规则是从右至左 1~31号。
如本月签到记录中data_list字段为29,转换成二进制为:

00000000 00000000 00000000 00011101

则本月签到日数为[1,3,4,5]

PHP实现取出&格式整理

1  $dataList = 29 ;  
2  $dataList <<= 1;  
3          $index = 1;
4          $list = [];
5          while ($dataList >= 2){  /
6              if (($dataList >>= 1) & 1){ 
7                  $list[] = $index;
8              }
9              $index++;
10         }

解释:

2.向左平移1位扩大 11101 => 111010 (乘2)
6.向右移1位(除2),再用 & 1判断$dataList末位是否为1
$dataList乘2又除2还是29,转成二进制是11101
1转成二进制是1
11101 ---------- 十进制29
00001 ---------- 十进制1
进行&运算得出上下都为1的部分 结果为1
题外话:这个方法可以用来查询某一天是否有签到,比如查本月第三天有签到用十进制的4,转成二进制100
11101 --------- 十进制29
00100 --------- 十进制4
&运算结果100,那证明这天有签到,如果为0则
7.如果符合第6行的条件,就将从1开始自增的$index push到$list

逐步分析:

$index为1

111010 >= 2 进入while循环
111010 >>= 1 值变为 11101
11101 & 00001 为 00001 == 00001 判断为 TRUE
$list 压入 $index 后为 [1]
$index自增 此时为2

11101 >= 2 进入while循环
11101 >>= 1 值变为 1110
1110 & 0001 为 0000 == 0001 判断为 FALSE
$index自增 此时为3

1110 >= 2 进入while循环
1110 >>= 1 值变为 111
111 & 001 为 001 == 001 判断为 TRUE
$list 压入 $index 后为 [1,3]
$index自增 此时为3

如此重复多2次 最后结果
$list为[1,3,4,5]

PHP实现更新

沿用上面的数据 当前$dataList = 29,二进制为11101,本月1,3,4,5号均已签到
假如今天为6号,用户点击签到,获取当前日期截取日为$day = 6
先取用户本月签到列表数据$dataListr = 29

$dataList += (1 << $day - 1);

然后将$dataList存回去数据库即可

解释:

先是 1 << --$day , 顺序是先自减, --$day 为 5 ,自减的原因是左移的时候第一步在第二位
然后1 << 5 , 二进制 000001 << 5 结果为 100000 , 十进制为32
注:因为每次签到所占的位是每日向左移,所以一般情况下转成十进制都会大于$dataList
这里最好做个判断是否大于$dataList

标签: none

添加新评论