前言
不会吧不会吧, 都2202年了还有人不会写签到? redis位图实现签到功能简单方便, 走过路过可不要错过呦!
基础知识
位图源头
在日常开发中, 我们会遇到需要存储大量 bool
类型数据的需求, 比如用户签到和用户登陆的记录等, 这个时候用mysql存储来说比较占用资源, 所以为了解决这个问题, redis
提供了位图数据结构(就是 位数组
), 每个 bool
值只占用1个位, 8个位组成一个字节, 这样存储空间的节约率不用我多说吧;
Mysql占用对比
mysql
存一个thinyint
需要占用1个字节(bool
类型默认为thinyint(1)
), 而且你还需要存一个主键Id(你不存也会自动隐性的帮你存一列), int
的话需要占用 4个字节, 不算其他光是这两个字段存储你就需要5个字节, 而在位图里面5个字节都够存40条记录了…
使用
需要先知道以下几点:
- 位图的内容实际上也就是字符串, 只不过是更改的个位的内容, 所以分为零存零取和整存零取;
- 位图的位数是会自动补位的, 比如你设置一个空键第8位为1, 则会自动补充前8位为0 (数组从0开始计数);
1 2 3 4 5 6 7 8 9 10 | 127.0.0.1:6379> setbit zero 0 1 // 设置第0位为true (integer) 0 127.0.0.1:6379> getbit zero 0 (integer) 1 127.0.0.1:6379> setbit zero 8 1 // 设置第8位为true (integer) 0 127.0.0.1:6379> getbit zero 8 (integer) 1 127.0.0.1:6379> getbit zero 7 // 前面会自动补位 (integer) 0 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // 通过python方法获取 h 的二进制 >>> bin(ord( "h" )) '0b1101000' 127.0.0.1:6379> set one h // 直接存入字符 h = 01101000 OK 127.0.0.1:6379> getbit one 0 (integer) 0 127.0.0.1:6379> getbit one 1 (integer) 1 127.0.0.1:6379> getbit one 2 (integer) 1 127.0.0.1:6379> getbit one 3 (integer) 0 127.0.0.1:6379> getbit one 4 (integer) 1 127.0.0.1:6379> getbit one 5 (integer) 0 |
功能实现
看完上面的基础知识大家就都基本知道怎么实现了, 下面是实战环节
需求
- 以自然周为周期进行签到;
- 展示签到周期;
- 重复签到提示报错;
流程图
签到周期获取
获取每个自然周的起始和结束时间
1 2 3 4 5 6 7 8 9 10 11 12 | // SignStartEndTime 签到起始时间&结束时间. func (slf *TaskService) SignStartEndTime() (startTime, endTime int64) { now := time.Now() weekDay := int (now.Weekday()) // 如果是周日的话weekDay=0 if weekDay == 0 { weekDay = 7 } startTime = time.Date(now.Year(), now.Month(), now.Day()-weekDay+ 1 , 0 , 0 , 0 , 0 , now.Location()).Unix() endTime = startTime + 86400 * 7 - 1 return } |
签到存储key
因为是以自然周为单位, 所以设定key的格式为 user:sign:userId:20221107(周一的日期)
1 2 3 4 5 | // SignKey 签到key. func (slf *TaskService) SignKey(userId string) string { st, _ := slf.SignStartEndTime() return fmt.Sprintf( "user:sign:%s:%s" , userId, time.Unix(st, 0 ).Format( "20060102" )) } |
签到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | const ( SignTypeNo = iota // 未签到 SignTypeYes // 已签到 ) // Sign 签到. func (slf *TaskService) Sign(userId string) (code errcode.ErrCode, err error) { var ( client = redis.GetClient() key = slf.SignKey(userId) weekDay = int64(time.Now().Weekday()) ) if weekDay == 0 { weekDay = 7 } val, err := client.SetBit(key, weekDay, SignTypeYes).Result() if err != nil { return errcode.ServerErr, fmt.Errorf( "sign setBit err: %v" , err) } if val == SignTypeYes { return 已签到错误码, fmt.Errorf( "sign already, val: %d" , val) } // 如果是第一次签到需要加个过期时间 signDay := client.BitCount(key, nil).Val() if signDay == 1 { client.Expire(key, time.Hour* 24 * 7 ) } if err = 签到成功获取的奖励; err != nil { // 如果添加奖励失败, 重置签到状态 if err = client.SetBit(key, weekDay, SignTypeNo).Err(); err != nil { log.Errorf( "sign reset err: %v" , err) } return errcode.ServerErr, fmt.Errorf( "sign add reward err: %v" , err) } return errcode.Code200, nil } |
结语
签到任务看着简单, 但实际上确实也不难, 但是我们在实现的时候要考虑到如何有效的节约和利用资源, 哪种实现方式会更好更优雅一些;
到此这篇关于PHP利用redis位图实现简单的签到功能的文章就介绍到这了,更多相关redis签到内容请搜索IT俱乐部以前的文章或继续浏览下面的相关文章希望大家以后多多支持IT俱乐部!