用户签到系统
如果相遇的目的是为了失去,那相遇的目的又是什么呢?
设计一个用户签到系统,并且这个签到系统支持7天,14天等不同天数的连续签到功能?
方案1 2张表
很暴力的做法,2张表去实现这个业务。
为了支持连续签到功能和不同天数的签到周期,我们需要设计合理的数据结构。假设使用关系型数据库(如 MySQL),以下是一些关键表的设计。
表1:用户签到记录表(user_signin_log
)
记录用户每天的签到情况。
字段名 | 类型 | 说明 |
---|---|---|
user_id |
BIGINT |
用户ID |
signin_date |
DATE |
签到日期 |
created_at |
DATETIME |
记录创建时间 |
说明:
signin_date
记录用户签到的日期。user_id
和signin_date
可以设置成复合主键,保证一天只能签到一次。
表2:用户连续签到表(user_signin_streak
)
记录用户的连续签到情况,方便快速查询。
说明 | 字段名 | 类型 |
---|---|---|
用户ID | user_id |
BIGINT |
当前连续签到天数 | current_streak |
INT |
上一次签到的日期 | last_signin_date |
DATE |
用户历史最长连续签到天数 | longest_streak |
INT |
是否已经领取奖励 | reward_claimed |
BOOLEAN |
连续签到重置日期(可选) | next_reset_date |
DATE |
说明:
current_streak
用于记录当前的连续签到天数。last_signin_date
是用户上次签到的日期,用于判断用户是否连续签到。longest_streak
记录用户历史上最长的连续签到天数。next_reset_date
(可选):如果用户的签到周期是 7 天或 14 天,则可以用这个字段来标记下一个周期的重置日期。
主要流程如下:
- 签到请求:用户发起签到请求。
- 检查是否已经签到:首先查询
user_signin_log
表,检查当天是否已经签到过。如果签到过,返回提示信息。 - 计算是否连续签到:
- 查询user_signin_streak表,获取用户上次签到的日期:
- 如果
last_signin_date
是昨天,则认为用户连续签到,更新current_streak
。 - 如果
last_signin_date
是今天,直接返回(避免重复签到)。 - 如果
last_signin_date
不是昨天,也不是今天,说明用户中断签到,重置current_streak
为 1。
- 如果
- 更新
user_signin_log
表和user_signin_streak
表,记录最新的签到情况。
- 查询user_signin_streak表,获取用户上次签到的日期:
- 奖励检查:如果用户的
current_streak
达到设定的天数(如7天、14天等),触发奖励逻辑。
方案2 Redis-BitMap
Redis 的 Bitmap 是一种基于二进制位操作的数据结构,特别适合类似签到这样按天记录的场景。每个用户可以对应一个 Bitmap,Bitmap 中的每个位表示用户在某一天是否签到,1
表示已签到,0
表示未签到。
关键实现:
- 签到操作:将当天对应的位设置为
1
。 - 查看某天是否签到:检查某天对应的位是
1
还是0
。 - 连续签到判断:将最近的几天(如7天)的位进行统计,判断是否连续签到。
实现步骤:
-
存储结构:
- 使用 Redis 的 Bitmap,每个用户对应一个 Bitmap 键,例如:
user_signin_1001
,其中1001
是用户 ID。 - 每天的签到对应 Bitmap 的一个位,位的索引可以是当天距离某个基准日的天数。例如,2023年1月1日是基准日,那么 2023年1月2日对应的位索引为 1,2023年1月3日对应的位索引为 2,如此类推。
- 使用 Redis 的 Bitmap,每个用户对应一个 Bitmap 键,例如:
-
签到操作:
- 使用
SETBIT
命令来设置某天的签到状态。
1
2# 用户 1001 在第 N 天签到
SETBIT user_signin_1001 N 1 - 使用
-
查询签到状态:
- 使用
GETBIT
查询用户某天是否签到。
1
2# 查询用户 1001 在第 N 天是否签到
GETBIT user_signin_1001 N - 使用
-
判断连续签到:
- 使用
BITFIELD
或GETBIT
查询最近 N 天的签到状态,然后遍历这些结果进行连续签到的判断。
1
2# 查询用户 1001 最近 7 天的签到情况
BITFIELD user_signin_1001 GET u7 0这里
u7
表示查询最近 7 位,即最近 7 天的签到记录。 - 使用
-
统计连续签到天数:
- 遍历最近 N 天的位,检查是否每天都为
1
。如果存在0
,则认为连续签到中断。
- 遍历最近 N 天的位,检查是否每天都为
缺点:
- 依赖 Redis:如果 Redis 发生数据丢失,签到数据也会丢失,可能需要额外的持久化或备份机制。
java实现
1 | import redis.clients.jedis.Jedis; |
我们用C++来实现下位图
1 |
|
方案3 MySQL-VARCHAR
在 MySQL 中,可以使用一个 VARCHAR
字段来表示用户每个月的签到记录。每个字符代表一天的签到状态,例如 0
表示未签到,1
表示已签到。
关键实现:
- 签到操作:更新
VARCHAR
中对应位置的字符为1
。 - 查看某天是否签到:查询
VARCHAR
中对应位置的字符。 - 连续签到判断:截取
VARCHAR
中连续的几天进行判断。
实现步骤:
-
存储结构:
- 每个用户有一个
VARCHAR(30)
字段,表示当前月份的签到情况,例如101010110100010111000101010101
。 - 每个字符表示一天的签到状态,
1
表示已签到,0
表示未签到。
1
2
3
4
5CREATE TABLE user_signin (
user_id BIGINT PRIMARY KEY,
signin_record VARCHAR(30), -- 每个字符代表一天
last_signin_date DATE
); - 每个用户有一个
-
签到操作:
- 获取用户的签到记录
signin_record
,并将当天对应的字符更新为1
,然后更新回数据库。
1
2
3UPDATE user_signin
SET signin_record = SUBSTRING(signin_record, 1, N-1) + '1' + SUBSTRING(signin_record, N+1)
WHERE user_id = ?; - 获取用户的签到记录
-
查询签到状态:
- 查询
signin_record
中第 N 位的字符,判断是否为1
。
1
2
3SELECT SUBSTRING(signin_record, N, 1)
FROM user_signin
WHERE user_id = ?; - 查询
-
连续签到判断:
- 查询
signin_record
中最后 N 天的字符,检查是否都是1
。
1
2
3SELECT SUBSTRING(signin_record, LENGTH(signin_record) - N + 1, N)
FROM user_signin
WHERE user_id = ?; - 查询
-
重置签到记录:
- 每个月可以重置
signin_record
字段为 30 个0
,用于新一轮的签到。
- 每个月可以重置
java实现
1 | import java.sql.*; |
方案4 MySQL-状态位
MySQL 基于状态位的实现
这种方法使用 last_signed_date
和 state
两个字段来实现连续签到的逻辑。last_signed_date
记录用户上次签到的日期,state
记录用户是否在连续签到。
关键实现:
- 签到操作:更新
last_signed_date
和state
。 - 判断连续签到:根据
last_signed_date
和state
来判断用户是否连续签到。
实现步骤:
-
存储结构:
1
2
3
4
5CREATE TABLE user_signin (
user_id BIGINT PRIMARY KEY,
last_signed_date DATE, -- 上次签到日期
state TINYINT -- 1 表示在连续签到,0 表示断签
); -
签到操作:
- 查询
last_signed_date
,如果上次签到日期是昨天,说明用户在连续签到,更新state
为1
。 - 如果上次签到日期不是昨天,说明用户断签了,更新
state
为0
。
1
2
3
4UPDATE user_signin
SET last_signed_date = CURDATE(),
state = IF(last_signed_date = CURDATE() - INTERVAL 1 DAY, 1, 0)
WHERE user_id = ?; - 查询
-
连续签到判断:
- 如果
state = 1
,说明用户在连续签到。 - 如果
state = 0
,说明用户已经断签。
- 如果
-
断签处理:
- 如果用户某天没签到,
state
保持为0
,直到用户重新开始连续签到。
- 如果用户某天没签到,
优点:
- 逻辑简单:只需要两个字段即可判断用户是否连续签到,容易实现。
- 数据库负担轻:只更新签到日期和状态位,操作简单。
缺点:
- 不支持查看历史记录:无法查看用户具体哪天签到,哪天没签到。
- 功能有限:只适合做简单的连续签到判断,无法展示详细的签到历史。
java实现
1 | import java.sql.*; |
评论