利用Redis bitmap签到功能

2023-11-19

1.简介

BitMap 存储的是连续的二进制数字(0 和 1),通过 bitmap, 只需要一个 bit 位来表示某个元素对应的值或者状态,key 就是对应元素本身 。我们知道 8 个 bit 可以组成一个 Byte,所以bitmap 本身会极大的节省储存空间。 bitmap最大为2的32次方个bit

setbit

  • SETBIT key offset value
  • key :设置的别名
  • offset:字节偏移量(二进制偏移量)
  • value:值(0和1),二进制中只有0和1两个数据

 getbit

  • GETBIT key offset

  • key :设置的别名

  • offset:字节偏移量(二进制偏移量)

bitcount

  • BITCOUNT key [start end]
  • key :设置的别名
  • start,end 二进制位的offset 开始和结束的角标位
  • 返回在这些角标位中二进制为1的数量,如果key为时间,可以统计出这一天中有多少个用户在活跃

BITOP

  • BITOP operation destkey key [key …]

  • operation : AND 、 OR 、 NOT 、 XOR

  • destkey : 合并目标key

  • key : 20200901

    例:BITOP and destkey 20200901 20200902

    合并两天的活跃用户数

2.使用场景
用户在线状态
统计活跃用户
用户签到

 /**
     * 签到页面数据
     * @return mixed|void
     */
    public function checkPage(){
        try {
            $uid = \request()->get('uid');
            return $this->sign->checkPage($uid);
        }catch (\Exception $exception){
            return error('请明天再签');
        }
    }

    /**
     * 签到
     * @return array|false|string
     */
    public function sign()
    {
        //开启事物
        DB::beginTransaction();
        try {
            $uid = \request()->get('uid');
            if (!is_numeric($uid) || $uid !=(int)$uid || $uid <= 0){
                return error('uid参数错误',);
            }
            $year = date('Ym');
            $num = request()->get('num');
            $date = date('Ymd');
            if ($date > $year.$num){
                DB::commit();
                return $this->sign->retroactive($uid,$year.$num);
            }elseif ($date < $year.$num){
                return error('时间未到');
            }
            //提交
            DB::commit();
            return $this->sign->Sign($uid);
        }catch (\Exception $exception){
            //回滚
            DB::rollBack();
            return error($exception->getMessage());
        }
    }
<?php

namespace App\Repository\Repositories;

use App\Models\IntegralRecord;
use App\Models\User;
use App\Models\UserSign;
use App\Repository\RepositoryInterface\SignInterface;
use Illuminate\Support\Facades\Redis;

class SignRepositories implements SignInterface
{

    /**
     * 签到页面数据
     * @param $uid
     * @return mixed|void
     */
    public function checkPage($uid)
    {
        $month = date('Ym');
        $day = date('d');
        $i = 0;
        $signKey = $uid . ':' . $month;
        $sign = Redis::getbit($signKey, $day);
        $user = (new User())->where('uid',$uid)->first()->toarray();
        if ($sign == 1) {
            //连续签到次数
            $min = self::getNum($uid, $month, $day,$i);
            return success('查询成功',['num' => $min,'day' => date('d'),'integral' => $user['integral'],'user' => $user,'year' => date('Y'),'month' => date('m')]);
        }
        return success('查询成功',['num' => $i,'day' => date('d'),'integral' => $user['integral'],'user' => $user,'year' => date('Y'),'month' => date('m')]);

    }

    /**
     * 签到
     * @param $uid
     * @return false|string
     */
    public function Sign($uid)
    {
        //获取当前时间
        $now = date('Ym');
        $day = date('d'); //04
        //拼接bitmap的key
        $key = $uid.':'.$now;  //"1:202207"
        //查看是否签到过
        $date = Redis::getbit($key,$day);
        if($date) return success('今天已签到');
        //获取昨天时间
        $lastDay = date("d", strtotime("-1 day"));  //"02"
        //查看昨天是否签到
        $lastDate = Redis::getbit($key,$lastDay);
        $user = User::where('uid',$uid)->first();
        //昨天签到(连签)
        if($lastDate){
            //正常所得积分30  额外所得积分50  一共80积分
            if ($user['sign_num'] == 5 || $user['sign_num'] == 11 || $user['sign_num'] == 17){
                $getIntegral = 80;
            }elseif($user['sign_num'] >= 3){
                $getIntegral = 30;
            }else{
                $getIntegral = 10;
            }
            $num = $user['sign_num'] + 1;
            $integralFen = $user['integral'] + $getIntegral;
            $result = User::where('uid',$uid)->update(['sign_num' => $num,'integral' => $integralFen]);
            if (!$result) return error('签到失败');
            $signRes = UserSign::insert(['uid' => $uid,'title' => "用户第$num 次签到",'number' => $getIntegral,'balance' => $integralFen,'add_time' => time()]);
            if (!$signRes) return error('签到失败');
            IntegralRecord::insert(['uid' => $uid,'integral_source' => '2','integral_increase' => $getIntegral,'add_time' => time()]);
            Redis::setbit($key,$day,1);
            return success("您已连续签到$num 天,已得积分$integralFen 分",['num' => $num,'day' => date('d'),'integral' => $integralFen]);
        }
        //昨天未签到(断签)
        $getIntegral = 10;
        $num = 1;
        $result = User::where('uid',$uid)->update(['sign_num' => $num,'integral' => $user['integral'] + $getIntegral]);
        if (!$result) return error('签到失败');
        $signRes = UserSign::insert(['uid' => $uid,'title' => "用户$uid 签到",'number' => $getIntegral,'balance' => $user['integral'] + $getIntegral,'add_time' => time()]);
        if (!$signRes) return error('签到失败');
        IntegralRecord::insert(['uid' => $uid,'integral_source' => 2,'integral_increase' => $getIntegral,'add_time' => time()]);
        Redis::setbit($key,$day,1);
        return success('签到成功',['num' => $num,'day' => date('d'),'integral' => $user['integral'] + $getIntegral]);
    }

    /**
     * 补签
     * @param $uid
     * @param $date
     * @return false|mixed|string
     */
    public function retroactive($uid, $date)
    {
        //获取今天的时间
        $now = date('Ymd');
        if ($date >= $now) return error('补签的日期不能大于等于今天');
        //获取要补签的时间
        $day = substr($date, 6, 2);
        $month = substr($date, 0, 6);
        $key = $uid . ':' . $month;
        $sign = Redis::getbit($key, $day); //0
        if ($sign == 1) return error('你已经签到过了不需要补签');
        Redis::setbit($key, $day, 1);
        //查询用户信息
        $user = User::where('uid',$uid)->first()->toarray();
        //每次补签需花费30积分
        $fire = 30;
        if($user['integral'] < $fire) return error('您的积分不足,不符合补签的条件');
        $integral = $user['integral'] - $fire;
        $month = date('Ym');
        $day = date('d') - 1;
        $signKey = $uid . ':' . $month;
        $sign = Redis::getbit($signKey, $day);
        $i = 1;
        if ($sign == 1) {
            //连续签到次数
            $i = self::getNum($uid, $month, $day,$i);
            User::where('uid',$uid)->update(['integral' => $integral,'sign_num' => $i]);
        }
        $signRes = UserSign::insert(['uid' => $uid,'title' => "用户$uid 补签",'number' => 0,'balance' => $integral,'add_time' => time()]);
        if (!$signRes) return error('签到失败');
        IntegralRecord::insert(['uid' => $uid,'integral_end' => 2,'integral_reduce' => $fire,'add_time' => time()]);
        return success("您补签成功",['num' => $i,'day' => date('d'),'integral' => $integral]);
    }

    /**
     * 计算连续签到的天数
     * @param $uid
     * @param $date
     * @param $day
     * @param $i
     * @return false|int|mixed|string
     */
    public static function getNum($uid, $date, $day,&$i)
    {
        try {
            $key = $uid . ':' . $date;
            $sign = 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);
                }
            }
            return $i;
        }catch (\Exception $e){
            return error($e->getMessage());
        }
    }
}

wxml

<!--index.wxml-->
<calendar defaultDate="{{toYear}}/{{toMonth}}/{{dateString}}" spot="{{list}}" bind:dateChange="dateChange"></calendar>
<view class="date-string">当前选中的日期是:{{date}}</view>

<view class='sign-com'>
<view class='thead'>
    <view class='tt'>已连续签到</view>
    <view class='mm'><label class='n'>{{signNum}}</label>天</view>
    <view class='pp'>你的积分为{{ score}}</view>
</view>
</view>

<view class="doc-box">
  <view class="title">属性</view>

  <view class="prpos">defaultDate</view>
  <view class="explain">日历默认选中的时间</view>
  <view class="type">String | Date | Number(可以被dayjs解析的格式即可)</view>
  <view class="default">今天</view>

  <view class="prpos">spot</view>
  <view class="explain">底部需要展示小圆点的日期数组</view>
  <view class="type">Array{{'<'}}String | Date | Number{{'>'}}</view>
  <view class="default">[]</view>

  <view class="title">事件</view>

  <view class="event">bind:dateChange</view>
  <view class="explain">选中的日期变化时触发</view>
  <view class="callback">event.detail:{ date, month, year, dateString }</view>

  <view class="event">bind:monthChange</view>
  <view class="explain">选中的月份变化时触发</view>
  <view class="callback">event.detail:{ date, month, year, dateString }</view>

  <view class="event">bind:yearChange</view>
  <view class="explain">选中的年份变化时触发</view>
  <view class="callback">event.detail:{ date, month, year, dateString }</view>
</view>

js

//index.js
Page({

  /**
   * 页面的初始数据
   */
  data: {
    signNum:0,//签到天数
    toMonth: parseInt(new Date().getMonth() + 1), //本月
    toYear: parseInt(new Date().getFullYear()), //本年
    dateString: parseInt(new Date().getDate()),
    // spot: ['2022/7/6', '2022/7/9'],
    list:[]
  },
  dateChange(e) {
    let that = this
    let num = e.detail.date
    console.log(num)
    if(num > this.data.dateString){
      wx.showToast({
        title: '时间未到',
        icon:'none'
      })
      return false
    }
    let ckl = this.data.toYear+'/'+this.data.toMonth+'/'+num
    let list = this.data.list
    list.push(ckl)
    // console.log(ckl)
    // console.log(list)
    this.setData({
      date:num,
      list:list
    })
    let uid = 1
    wx.request({
      url: 'http://www.ckl.com/api/sign', //仅为示例,并非真实的接口地址
      data: {
        num,
        uid
      },
      header: {
        'content-type': 'application/json' // 默认值
      },
      success (res) {
        console.log(res.data)
        that.setData({
          signNum:res.data.data.num,
          score:res.data.data.integral
        })
      }
    })
  },
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    let that = this
    let uid = 1
    wx.request({
      url: 'http://www.ckl.com/api/checkPage', //仅为示例,并非真实的接口地址
      data: {
        uid
      },
      header: {
        'content-type': 'application/json' // 默认值
      },
      success (res) {
        console.log(res.data)
        let year = res.data.data.year
        let month = res.data.data.month
        let num = res.data.data.num
        let day = res.data.data.day
        for(let i=num;i>0;i--){
          let ckl = year+'/'+month+'/'+day
          day--
          let list = that.data.list
          list.push(ckl)
          that.setData({
            list:list
          })
        } 
        that.setData({
          signNum:res.data.data.num,
          score:res.data.data.integral
        })
      }
    })
  },



})

wxss

/**index.wxss**/
page {
  background-color: #f6f6f6;
}

.date-string {
  margin-top: 20rpx;
  background-color: #fff;
  font-size: 28rpx;
  padding: 0 30rpx;
  line-height: 60rpx;
}


.sign-com {
  width: 100%;
  height: auto;
  padding: 0 30rpx;
  box-sizing: border-box;
  overflow: hidden;
}

.sign-com .thead {
  width: 100%;
  text-align: center;
  padding: 50rpx 0 35rpx;
}

.sign-com .thead .tt {
  font-size: 24rpx;
}

.sign-com .thead .mm {
  margin-top: 10rpx;
  font-size: 24rpx;
}

.sign-com .thead .mm .n {
  font-size: 66rpx;
  margin-right: 25rpx;
}

.sign-com .thead .pp {
  color: #999;
  font-size: 24rpx;
  margin-top: 10rpx;
}

第二种

wxml

<view class='signIn'>
    <view class='explax'>
        <view class=''><image src="{{avatar}}" style="border-radius: 60%;width: 60px;height: 60px;float: left"></image></view>

        <view class='' style="font-size: 18px;font-weight: 800;float: left;margin-left: 10px;">{{nickname}}</view>
    </view>
    <view class='sign-com'>
        <view class='thead'>
            <view class='tt'>已连续签到</view>
            <view class='mm'><label class='n'>{{signNum}}</label>天</view>
            <view class='pp'>你的积分为{{ score}}</view>
        </view>
        <view class='modle'>
            <view class='mol'>
                <view class='mol-line'></view>
                <view class='mol-ites'>
                    <view class="ite {{signNum>=4?'hover':''}}" data-n='{{min-3}}'>
                        <label class='n'>+{{signNum<7?10:30}}</label>
                    </view>
                    <view class="ite {{signNum>=3?'hover':''}}" data-n='{{min-2}}'>
                        <label class='n'>+{{signNum<7?10:30}}</label>
                    </view>
                    <view class="ite {{signNum>=2?'hover':''}}" data-n='{{min-1}}'>
                        <label class='n'>+{{signNum<4?10:30}}</label>
                    </view>
                    <view class="ite {{signNum>=1?'hover':''}}" data-n='{{min}}'>
                        <label class='n'>+{{signNum<3?10:30}}</label>
                    </view>
                    <view class="ite {{signNum>=min+4?'hover':''}}" data-n='{{min+1}}'>
                        <label class='n'>+{{signNum+1<3?10:30}}</label>
                    </view>
                    <view class="ite {{signNum+2>7?'hover':''}}" data-n='{{min+2}}'>
                        <label class='n'>+{{signNum+2<3?10:30}}</label>
                    </view>
                    <view class="ite {{signNum+2>7?'hover':''}}" data-n='{{min+3}}'>
                        <label class='n'>+{{signNum+2<3?10:30}}</label>
                    </view>
                </view>
            </view>
            <view class='moday'>
                <label class='dd' bindtap='bindSignIn' data-num="{{min-3}}">{{min-3}}号</label>
                <label class='dd' bindtap='bindSignIn' data-num="{{min-2}}">{{min-2}}号</label>
                <label class='dd' bindtap='bindSignIn' data-num="{{min-1}}">{{min-1}}号</label>
                <label class='dd' bindtap='bindSignIn' data-num="{{min}}">{{min}}号</label>
                <label class='dd' bindtap='bindSignIn' data-num="{{min+1}}">{{min+1}}号</label>
                <label class='dd' bindtap='bindSignIn' data-num="{{min+2}}">{{min+2}}号</label>
                <label class='dd' bindtap='bindSignIn' data-num="{{min+4}}">{{min+3}}号</label>
            </view>
        </view>

    </view>
</view>
<view class='explax'>
    <view class=''>签到数:{{signNum}}天</view>
    <view class=''>您的积分为:{{score}}</view>
</view>

js

// pages/sign/sign.js
Page({

  /**
   * 页面的初始数据
   */
  data: {
     days: [],
				SignUp: [],
				cur_year: 0, //当前选的年
				cur_month: 0, //当前选的月
				min: parseInt(new Date().getDate()), //本日
				toMonth: parseInt(new Date().getMonth() + 1), //本月
				toYear: parseInt(new Date().getFullYear()), //本年
				weeks_ch: ['日', '一', '二', '三', '四', '五', '六'],
				weeks_en: ['Sun', 'Mon', 'Tues', 'Wed', 'Thur', 'Fri', 'Sat'],
  },
  bindSignIn(c){
    console.log(c)
    let that = this
    let num = c.currentTarget.dataset.num
    let uid = 1
    wx.request({
      url: 'http://www.ckl.com/api/sign', //仅为示例,并非真实的接口地址
      data: {
        num,
        uid
      },
      header: {
        'content-type': 'application/json' // 默认值
      },
      success (res) {
        console.log(res.data)
        that.setData({
          signNum:res.data.data.num,
          score:res.data.data.integral
        })
      }
    })
  },
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad(options) {
    let that = this
    let uid = 1
    wx.request({
      url: 'http://www.ckl.com/api/checkPage', //仅为示例,并非真实的接口地址
      data: {
        uid
      },
      header: {
        'content-type': 'application/json' // 默认值
      },
      success (res) {
        console.log(res.data)
        that.setData({
          signNum:res.data.data.num,
          score:res.data.data.integral,
          nickname:res.data.data.user.nickname,
          avatar:res.data.data.user.avatar
        })
      }
    })
  },



})

wxss

.signIn {
  width: 100%;
  height: 220px;
}

.sign-com {
  width: 100%;
  height: auto;
  padding: 0 30rpx;
  box-sizing: border-box;
  overflow: hidden;
}

.sign-com .thead {
  width: 100%;
  text-align: center;
  padding: 50rpx 0 35rpx;
}

.sign-com .thead .tt {
  font-size: 24rpx;
}

.sign-com .thead .mm {
  margin-top: 10rpx;
  font-size: 24rpx;
}

.sign-com .thead .mm .n {
  font-size: 66rpx;
  margin-right: 25rpx;
}

.sign-com .thead .pp {
  color: #999;
  font-size: 24rpx;
  margin-top: 10rpx;
}

.sign-com .modle {
  width: 100%;
  height: 100rpx;
  margin-top: 10rpx;
}

.sign-com .modle .mol {
  width: 100%;
  height: 52rpx;
  position: relative;
}

.sign-com .mol-line {
  width: 100%;
  height: 4rpx;
  background-color: #e6e6e6;
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
}

.sign-com .mol-ites {
  width: 100%;
  height: 100%;
  position: absolute;
}

.mol-ites .ite {
  width: 52rpx;
  height: 52rpx;
  border-radius: 50%;
  border: 1px solid #f5f5f5;
  background-color: #fff;
  box-sizing: border-box;
  position: absolute;
  left: 0;
  top: 0;
  z-index: 2;
}

.mol-ites .ite .n {
  width: 44rpx;
  height: 44rpx;
  line-height: 44rpx;
  text-align: center;
  border-radius: 50%;
  background-color: #f5f5f5;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  font-size: 22rpx;
}

.mol-ites .ite::after {
  content: "";
  width: 80rpx;
  height: 4rpx;
  background-color: transparent;
  position: absolute;
  left: 52rpx;
  top: 50%;
  margin-top: -2rpx;
  z-index: 2;
}

.mol-ites .ite:last-of-type::after {
  width: 0;
}

.mol-ites .ite:nth-of-type(2) {
  left: 107rpx;
}

.mol-ites .ite:nth-of-type(3) {
  left: 214rpx;
}

.mol-ites .ite:nth-of-type(4) {
  left: 321rpx;
}

.mol-ites .ite:nth-of-type(5) {
  left: 428rpx;
}

.mol-ites .ite:nth-of-type(6) {
  left: 535rpx;
}

.mol-ites .ite:nth-of-type(7) {
  left: 642rpx;
}

.mol-ites .ite.hover {
  border-color: #ff614a;
}

.mol-ites .ite.hover .n {
  background-color: #ff614a;
  color: #fff;
}

.mol-ites .ite.hover::after {
  background-color: #ff614a;
}

.moday {
  width: 100%;
  height: 80rpx;
  overflow: hidden;
  position: relative;
  margin-top: 20rpx;
}

.moday .dd {
  width: 52rpx;
  height: 80rpx;
  line-height: 1;
  text-align: center;
  font-size: 22rpx;
  position: absolute;
  left: 0;
  bottom: 0;
}

.moday .dd:nth-of-type(2) {
  left: 107rpx;
}

.moday .dd:nth-of-type(3) {
  left: 214rpx;
}

.moday .dd:nth-of-type(4) {
  left: 321rpx;
}

.moday .dd:nth-of-type(5) {
  left: 428rpx;
}

.moday .dd:nth-of-type(6) {
  left: 535rpx;
}

.moday .dd:nth-of-type(7) {
  left: 642rpx;
}

.the-btn {
  margin: 50rpx 0;
}

.the-btn .btn {
  background-color: #ff614a;
  color: #fff;
}

.the-btn.signed .btn {
  background-color: rgba(153, 153, 153, 0.61);
}

.explax {
  margin-top: 40px;
  padding: 0 30rpx;
  font-size: 28rpx;
  color: #666;
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

利用Redis bitmap签到功能 的相关文章

随机推荐