签到表需设continuous_days字段实时维护连续天数,通过事务+唯一索引+redis缓存+幂等token保障高并发下数据准确。

签到数据表设计要包含连续天数字段
连续签到的核心不是靠每天查历史记录累加,而是维护一个 continuous_days 字段。每次签到前先查上一次签到日期,如果间隔为 1 天,就 +1;否则重置为 1。不依赖 COUNT(*) 或子查询统计,避免性能问题和时区干扰。
表结构建议至少包含:user_id(主键/唯一索引)、last_sign_at(datetime,记录上次签到时间)、continuous_days(tinyint,默认 1)、total_sign_days(int,累计总次数,可选)。不要用「签到记录表」+「聚合查询」来算连续天数,高并发下容易出错。
- 务必给
user_id加唯一索引,防止重复插入或更新冲突 -
last_sign_at必须用数据库当前时间(如NOW()),别用 PHP 的date('Y-m-d H:i:s'),避免服务器时间不准导致跨日判断失败 - 如果业务需要保留明细,可另建
sign_log表,但连续逻辑只读写主表
ThinkPHP 中用 Db::transaction 保证签到原子性
签到涉及「读上一次记录 → 判断间隔 → 更新连续天数 → 写新记录」,必须包裹在事务里,否则并发请求可能让两个请求都读到同一条旧记录,导致连续天数只 +1 而非正确递增或重置。
示例逻辑(TP6+):
Db::transaction(function () use ($userId) {
$record = Db::name('user_sign')->where('user_id', $userId)->find();
$today = date('Y-m-d');
$lastDate = $record ? date('Y-m-d', strtotime($record['last_sign_at'])) : '';
<pre class="brush:php;toolbar:false;">if ($lastDate === $today) {
throw new Exception('今日已签到');
}
$continuous = $lastDate === date('Y-m-d', strtotime('-1 day'))
? $record['continuous_days'] + 1
: 1;
Db::name('user_sign')->update([
'user_id' => $userId,
'last_sign_at' => date('Y-m-d H:i:s'),
'continuous_days' => $continuous,
'total_sign_days' => $record['total_sign_days'] + 1,
]);});
- 别用
Db::table()->lock(true)模拟行锁,TP 的 lock 在 MySQL 中实际是SELECT ... FOR UPDATE,但需确保事务开启且隔离级别支持,不如直接用transaction稳定 - 检查是否今日已签到,必须比对「日期部分」,不能直接比
last_sign_at >= today 00:00:00,否则跨分钟/秒的请求可能漏判 - 更新语句要用
update()而非save(),避免模型自动过滤字段或触发多余钩子
获取连续签到天数时别直接查数据库
用户中心展示「已连续签到 X 天」,这个值应该从缓存取,而不是每次请求都查 user_sign 表。尤其当首页、个人页高频访问时,数据库压力明显。
推荐方案:签到成功后,用 Redis 存 sign:uid:{user_id},值为 continuous_days,过期时间设为 2 天(覆盖跨日场景)。读取时优先 $redis->get('sign:uid:'.$uid),未命中再查库并回填缓存。
- Redis key 建议带版本号,比如
sign:v1:uid:{user_id},方便后续逻辑变更时批量失效 - 不要用 ThinkPHP 的
Cache::get()封装层去存连续天数——它默认用文件驱动,高并发下有锁竞争,且不支持原子自增 - 如果用户修改了系统时间(如手动调早),会导致
last_sign_at异常,可在签到时加校验:abs(strtotime($now) - time()) (5 分钟容差)
前端防重复提交和后端幂等校验都要做
用户狂点签到按钮,前端没限制,后端又没校验,就会出现「点一次,扣多次积分、发多次奖励」。光靠前端 disabled 不可靠,网络抖动或刷新后状态丢失照样会重复提交。
后端必须加幂等控制:签到接口接收一个客户端生成的 sign_token(如 UUID v4),单次有效,签到成功后立即写入 sign_token_log 表并加唯一索引。下次再传相同 token 直接返回「已处理」。
-
sign_token必须由前端在页面加载时生成并隐藏域存储,不能由后端分配——否则刷新页面 token 变更,用户误以为能重试 - 别用时间戳 + 用户 ID 拼接作 token,碰撞概率低但不为零;UUID 是最简单可靠的方案
- token 表的
token字段必须加UNIQUE索引,利用数据库唯一约束拦截重复,比 PHP 层判断更安全
连续签到真正的难点不在逻辑,而在「时间边界判断」和「并发更新一致性」——这两个地方一松懈,数据就悄悄错位,而且很难被测试覆盖到。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











