Go中用SETBIT签到需校准日期偏移量:2026-07-14对应offset=13而非14,须用time.Now().In(loc).Day()-1并统一时区格式化Key;GETBIT返回int64,应判res==1;连续签到需跨月查BITPOS而非仅BITCOUNT;过期用EXPIRE更安全。

Go 里用 SETBIT 签到前必须校准日期偏移量
偏移量不是「几号」,而是「几号减 1」。今天是 2026-07-14,那当天在位图里的位置是 13,不是 14。很多初学者直接传 time.Now().Day(),结果签到总错一天。
更隐蔽的问题是时区:如果服务跑在 UTC 时区,而用户在东八区,time.Now().Day() 可能返回 13 而非 14 —— 这会导致用户在本地 00:05 签到,却写到上月最后一位。
- 务必用
time.Now().In(loc).Day() - 1,其中loc是用户所在时区(如time.FixedZone("CST", 8*60*60)) - Key 中的年月也要同步用该时区格式化:
time.Now().In(loc).Format("200601") - 不要依赖服务器本地时区,也不要硬编码
time.Local—— 它可能被 Docker 或 systemd 修改
GETBIT 返回的是 int64,不是 bool
Go 的 redis.Client.GetBit 方法返回 int64 和 error,不是布尔值。直接拿返回值跟 true 比较会编译失败或逻辑出错。
常见错误写法:if res, _ := rdb.GetBit(ctx, key, offset).Result(); res == true { ... } —— 这里 res 是 int64,永远不等于 true。
立即学习“go语言免费学习笔记(深入)”;
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 正确判断方式:
res == 1表示已签,res == 0表示未签,res == -1通常表示 key 不存在(注意:Redis 对不存在的 key 默认返回 0,但某些客户端封装可能不同) - 别忽略 error:网络超时或 key 不存在时 error 不为 nil,此时 res 值不可信
- 如果用
github.com/go-redis/redis/v9,记得传 context,超时控制不能只靠 defer
统计连续签到天数不能只查当月 BITCOUNT
BITCOUNT 只能算「总共签了多少天」,没法直接得出「连续多少天」。连续签到必须从当天往前逐位查 GETBIT,直到遇到 0 或跨月边界。
容易踩的坑是只查当前月:用户 6 月 30 日签到、7 月 1 日签到,但若只查 sign:123:202607,会误判为「连续 1 天」而非「连续 2 天」。
- 先查当月:从
today.Day() - 1往前遍历,每遇到 1 就累加,遇到 0 就停 - 若当月全满(比如 7 月 14 日且前 14 天全为 1),再查上月 key
sign:123:202606的最后一天是否为 1;更稳妥的是用BITPOS sign:123:202606 0 -1查最后一个 0 的位置,推算末尾连续段长度 - 跨年场景同理,但注意 2 月天数不固定,别硬写 28/29 —— 用
time.Date(year, month+1, 0, 0, 0, 0, 0, loc).Day()动态算
每月重置不用删 key,用 EXPIRE 更安全
按月建 key(如 sign:123:202607)本就天然隔离,但有人会写定时任务去 DEL 上月 key。这有风险:DEL 是阻塞操作,key 很大时可能卡住 Redis 主线程;而且万一删错月份(比如把 202607 当成 202606),数据就丢了。
更稳的做法是让 key 自动过期。
- 签到时顺手设置过期时间:
rdb.Expire(ctx, key, 32*24*time.Hour)(覆盖整月+缓冲) - 避免用
SETEX替代SETBIT——SETEX会覆盖整个位图,导致历史签到丢失 - 如果必须手动清理(比如审计需要保留 12 个月),用
SCAN+DEL异步删,别用KEYS
位图真正的复杂点不在命令调用,而在时间边界和跨月逻辑——这些地方没测全,上线后连续签到奖励就发错。

















