在很多时候我们会遇到用户签到的场景,每天用户进入应用时,需要获取用户当天的签到状态,如果没签到,用户可以进行签到,并且得到相关的奖励。我们可能需要每天的签到情况,必要的时候可能还需要统计一下每天用户签到人数。 我们用Redis的Set数据结构可以轻松实现这个功能——以日期为key,以用户ID(对应着数据库的primary id)组成的集合为value,每当需要查询某个用户的签到状态时,只需要使用命令SISMEMBER key member就可以轻易得到想要的结果;用户签到时,使用命令SADD key member把用户ID添加到相应的日期中;统计某天用户的签到人数,可以用命令SCARD key。 以上的做法操作简便,易于理解,但是本篇要介绍的是另一种做法,使用Redis的位操作(bitmap)。 我们都知道数据在机器上存储的最小单元是位(bit),1位可以存储0和1两种状态。这里的场景需要存储正是签到和未签到两种状态,因此一个用户只需要占用1位,也就是用位操作比用集合操作要省很多空间,下面先说一下位操作的方式,最后会给出两种方式的内存占用对比。
//redis签到
/**
* @param $date //日期格式 20001229
* @param $user_id // 用户id
* @return array //数组
* 活动按照当天签到那些用户
*/
public static function ActivitiesIn($date,$user_id){
if($date==''){
$times=date('Ymd');
}else{
$times=$date;
}
$redis_key='login:'.$times;
$user_ture=Redisd::getInstance()->conn->getBit($redis_key,$user_id);
if ($user_ture==1){
$num=Redisd::getInstance()->conn->bitCount($redis_key);
return ['code'=>200,'msg'=>'已经签到了不需要在签到了','data'=>$num];
}
Redisd::getInstance()->conn->setBit($redis_key,$user_id,1);
$num=Redisd::getInstance()->conn->bitCount($redis_key);
return ['code'=>200,'msg'=>'签到成功','data'=>$num];
}
/**
* @param $user_id //用户id
* @param $year_mouth //年月格式 202206
* @param $day //当月天数
* @return array //数组
*/
public static function ASingleSignIn($user_id,$year_mouth,$day)
{
$Ymtimes=self::dateYm($year_mouth);
$times=self::dateTimes($day);
$redis_key='user:sing:'.$user_id.':'.$Ymtimes;
$user_ture=Redisd::getInstance()->conn->getBit($redis_key,$times);
if ($user_ture==1){
$nums=self::asff($user_id);
$num=Redisd::getInstance()->conn->bitCount($redis_key);
return ['code'=>200,'msg'=>'已经签到了不需要在签到了','num'=>$num,'nums'=>$nums];
}
Redisd::getInstance()->conn->setBit($redis_key,$times,1);
$num=Redisd::getInstance()->conn->bitCount($redis_key);
$nums=self::asff($user_id);
if ( $nums >= 3 ){
IntegralService::IntegralIncrease($user_id,30);
}
if ( $nums < 3 ){
IntegralService::IntegralIncrease($user_id,10);
}
return ['code'=>200,'msg'=>'签到成功','num'=>$num,'nums'=>$nums];
}
/**
* 获取当前年月
*
* 需要参数:$data_time
*/
public static function dateYm($data_time){
if (empty($data_time)){
// 如果传入年月为空,返回当前年月
return date('Ym');
}
// 不为空返回输入日期
return $data_time;
}
/**
* 获取日期 当月的日期 -1 为位图下标
*
* @param $data_time
*/
public static function dateTimes($dated)
{
if (!empty($deted)){
// 如果传入日期为空那么就返回当前日期-1
return date('d',strtotime("-1 day"));
}
// 不为空返回输入日期-1
return $dated-1;
}
/**
* 获取本月当前连续签到天数
*通过逆循环判断当天连续签到多少天
* 要判断当天是否签到来返回签到数
*
*/
public static function asff($user_id){
$signCount=0;
// 获取当前年月
$Ym=date('Ym');
// 获取当天天数
$Day=date('d',strtotime("-1 day"));
// 拼接kid
$redis_key='user:sing:'.$user_id.':'.$Ym;
// 逆循环得到当前连续签到天数
/**
* 做一个判断
* 设定当前用户当天是否签到,如果签到了,那么连续签到返回,如果没签到会返回零,所以进行判断
*/
if ( Redisd::getInstance()->conn->getBit($redis_key,$Day)!=1){
//如果当天没签到 那么再次往前一天计算
for ($i=$Day-1;$i>=0;$i--){
$yue= Redisd::getInstance()->conn->getBit($redis_key,$i);
if ($yue==1){
$signCount=$signCount+1;
}else{
break;
}
}
return $signCount;
} else{
// 如果当天签到那么返回当天计算
for ($i=$Day;$i>=0;$i--){
$yue= Redisd::getInstance()->conn->getBit($redis_key,$i);
if ($yue==1){
$signCount=$signCount+1;
}else{
break;
}
}
return $signCount;
}
}