基于redis实现签到功能

60 阅读2分钟

做一件事,无论大小,倘无恒心,是很不好的。而看一切太难,固然能使人无成,但若看得太容易,也能使事情无结果。

前言

每日签到这个功能相当常见,不管什么app都有可能碰到这个需求,那么我们如何去实现呢?其实很简单,我们可以通过bitmap去实现

什么是bitmap

Redis的Bitmap(位图)是一种数据结构,它可以用来存储和操作一组二进制位。Redis将位图作为整数数组来处理,每个数组元素都表示一个二进制位,可以存储0或1。Redis支持对位图进行位与、位或、位异或、位取反等操作。

以下是Redis官方对Bitmap的解释:

  • Bitmap可以用来存储二进制数据,每个bit都可以独立地设置、读取和删除。
  • Bitmap可以作为普通的数据类型进行存储和操作,也可以和其他数据类型进行组合使用。
  • Bitmap支持对位进行各种操作,包括位与、位或、位异或、位取反等,这些操作都可以在O(1)时间复杂度内完成。
  • Bitmap可以用来实现一些常见的数据结构,如哈希表、有序集合等。

在Redis中,可以使用以下命令对Bitmap进行操作:

  • BITCOUNT key:统计指定key中设置为1的bit数量。
  • BITOP operation destkey key:对一个或多个key进行位操作,并将结果保存到destkey中。
  • GETBIT key offset:获取指定key中指定offset位置的bit值。
  • SETBIT key offset bit:设置指定key中指定offset位置的bit值。

想要具体了解,网上有很多文章,这里就不多赘述了,下面就是直接上代码,实现一个每日签到功能

package main

import (
    "context"
    "fmt"
    "github.com/go-redis/redis/v9"
    "strconv"
    "time"
)

var ctx = context.TODO()

func CheckIn(client *redis.Client, userId int, day string) error {
    now := time.Now()

    key := fmt.Sprintf("user:%s_checkin:%d", fmt.Sprintf("%d%d", now.Year(), now.Month()), userId)
    return client.SetBit(ctx, key, dateToInt64(day), 1).Err()
}

func dateToInt64(str string) int64 {
    // 使用ParseInt函数进行转换
    num, _ := strconv.ParseInt(str, 10, 64)
    return num
}

func IsCheckIn(client *redis.Client, userId int, day string) bool {
    now := time.Now()

    key := fmt.Sprintf("user:%s_checkin:%d", fmt.Sprintf("%d%d", now.Year(), now.Month()), userId)
    return client.GetBit(ctx, key, dateToInt64(day)).Val() == 1
}

func CountCheckIn(client *redis.Client, userId int) int64 {
    now := time.Now()

    key := fmt.Sprintf("user:%s_checkin:%d", fmt.Sprintf("%d%d", now.Year(), now.Month()), userId)

    return client.BitCount(ctx, key, &redis.BitCount{
       Start: 0,
       End:   -1,
    }).Val()
}

func main() {
    client := redis.NewClient(&redis.Options{
       Addr: "127.0.0.1:6379",
       DB:   0,
    })

    // 模拟签到
    fmt.Println("Simulated check-in")
    for i := 1; i <= 31; i++ {
       if i%2 == 0 {
          fmt.Println("day: ", i)
          err := CheckIn(client, 1, strconv.Itoa(i))
          if err != nil {
             fmt.Println(err)
          }
       }
    }

    fmt.Println()

    // 判断签到
    fmt.Println("Determine whether to check-in or not")
    for i := 1; i <= 5; i++ {
       fmt.Println("day: ", i, " ", IsCheckIn(client, 1, strconv.Itoa(i)))
    }

    fmt.Println()

    // 统计签到
    fmt.Println("count check-in")
    fmt.Println(fmt.Sprintf("Users check-in for a total of %d days", CountCheckIn(client, 1)))
}

打印结果:

image.png