连续签到的核心是判断日期维度的连续性而非时间戳精度,应使用date类型字段存储自然日并统一时区,避免用秒级时间戳导致跨天误判、索引性能差及业务日不一致问题。

连续签到的核心不是存时间戳,而是识别“是否断连”——关键在日期维度的连续性判断,而非时间戳精度。
为什么不能直接用 time() 存签到记录
用秒级时间戳(如 1717023456)存签到,会导致无法可靠判断“连续”:用户下午 3 点签到、次日早上 8 点签到,两个时间戳差值约 17 小时,但日期上仍是连续两天;而若只比对差值是否等于 86400,会漏掉跨天但非整点的合法连续情况。
- 时间戳本身不携带日期边界信息,需额外转换才能做“日粒度”比对
- 服务器时区、客户端本地时间、夏令时都可能让
strtotime('today')和实际业务“自然日”错位 - 数据库索引对
FROM_UNIXTIME(timestamp)计算字段不友好,查询连续天数时性能骤降
推荐表结构:用 date 字段替代 timestamp
签到表应以“自然日”为单位去重和判定连续性,date 字段设为 DATE 类型(如 '2024-05-30'),配合唯一索引防重复签到。
CREATE TABLE user_signin ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, sign_date DATE NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_date (user_id, sign_date) );
-
sign_date由 PHP 用date('Y-m-d')生成,强制对齐业务日(建议统一用 UTC 或系统所在时区,避免date_default_timezone_set()漏配) - 不要用
INT存 yyyymmdd 格式数字——虽可索引,但丧失日期函数支持,后续查“本月第几天”或“周几”需字符串拆解 - 删除
created_at的自动更新逻辑(ON UPDATE CURRENT_TIMESTAMP),它对签到无意义且干扰审计
查连续天数:用变量法绕过窗口函数兼容性问题
MySQL 5.7 及以下不支持 LAG(),但可用用户变量模拟“上一条记录日期”,逐行比对是否相差 1 天。注意必须按 sign_date 严格升序扫描。
SELECT MAX(streak) AS max_streak FROM (
SELECT
@streak := IF(DATEDIFF(sign_date, @prev_date) = 1, @streak + 1, 1) AS streak,
@prev_date := sign_date
FROM user_signin
CROSS JOIN (SELECT @streak := 0, @prev_date := NULL) AS _init
WHERE user_id = 123
ORDER BY sign_date
) AS streaks;
- 必须加
ORDER BY sign_date,否则变量赋值顺序不可控 - 首次记录时
@prev_date为NULL,DATEDIFF(sign_date, NULL)返回NULL,触发IF的 false 分支,正确初始化为 1 - 该语句不能直接用于 Laravel 的
DB::select(),因 PDO 默认禁用多语句;需改用原生连接或拆成两步(先查有序日期列表,PHP 层遍历判断)
重置连续计数的边界条件必须显式定义
“连续”是否包含今天?昨天没签,今天补签能否接上?这些业务规则不会自动生效,必须在写入和查询时同步约束。
- 补签(
sign_date小于当前自然日)应拒绝,或走独立补签流程(另建表),否则会污染连续逻辑 - 查询当前连续天数时,必须限定
sign_date ,防止未来日期(如系统时间错误写入)被误计入 - 用户注销/封禁后,其历史连续记录仍有效,但新签到应拦截——逻辑在应用层控制,不在数据库里删数据
连续签到真正的复杂点从来不在算法,而在“哪天算一天”的定义一致性:PHP 的 date('Y-m-d')、MySQL 的 CURDATE()、前端传参的时区、cron 任务的执行时区,四者必须对齐,差一小时就可能让用户某天“签了却不算”。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











