签到打卡系统需确保用户当日唯一签到、准确记录时间、动态更新连续天数并支持合规补卡。核心实现包括:数据库建user_sign_log表并设联合唯一索引;控制器校验登录态与日期,事务内保存记录并更新用户状态;模型层比对历史签到日期差值更新continuous_days;补卡限7日内、排除已签/补卡日且不计连续天数;统一用服务端时间,高并发下可用Redis锁防重。

签到打卡系统的核心在于判断用户当天是否已签到、记录签到时间、累计连续签到天数,并支持补卡等扩展功能。Yii 框架下实现时,应围绕模型验证、数据库事务、时间逻辑和用户状态更新来组织代码,避免重复签到、时间错乱或并发问题。
数据库设计:签到记录表关键字段
建议新建 user_sign_log 表,包含以下必要字段:
- user_id:关联用户主键(非空,加索引)
- sign_date:DATE 类型,存储签到日期(如 '2024-06-15'),用于唯一约束
- created_at:INT,记录精确到秒的签到时间戳
- is_makeup:TINYINT(1),标识是否为补卡(0=正常签到,1=补卡)
- ip:VARCHAR(45),可选,记录客户端 IP 防刷
在 sign_date + user_id 上建立联合唯一索引,防止同日重复签到。
签到动作核心逻辑(Controller 层)
在控制器中处理签到请求,需校验登录态、防重提交、控制频率,并用事务保障数据一致性:
- 检查用户是否已登录(
Yii::$app->user->isGuest === false) - 获取当前日期(
$today = date('Y-m-d')),不依赖客户端传参 - 查询该用户当日是否存在记录(用
SignLog::find()->where(['user_id'=>$uid, 'sign_date'=>$today])->exists()) - 若存在,返回「今日已签到」;否则新建记录并开启事务保存
- 事务内同步更新用户主表的
last_sign_at和continuous_days字段(见下节)
连续签到天数更新策略(Model 层)
连续天数不能简单 +1,必须比对上一次签到日期是否为昨日:
- 查出用户最近一次签到记录(按
sign_date DESC排序取第一条) - 若无历史记录 →
continuous_days = 1 - 若有,计算差值:
$diff = (strtotime($today) - strtotime($lastSignDate)) / 86400 - 若 $diff == 1 → 连续,
continuous_days += 1 - 若 $diff > 1 → 中断,
continuous_days = 1 - 若 $diff == 0 → 今日已签,不触发此逻辑(由上层拦截)
注意:补卡不参与连续天数累计,需在查询“最近签到”时排除 is_makeup = 1 的记录。
补卡功能实现要点
补卡需额外权限控制与时间限制:
- 仅允许补「过去 7 天内未签到的日期」(含今天往前推 6 天)
- 前端传入待补日期
$makeupDate,后端强制校验:if (strtotime($makeupDate) time())→ 拒绝 - 检查该日期是否已有签到或补卡记录(同一日期只允许一次)
- 插入新记录时设置
is_makeup = 1,且不更新用户连续天数字段
补卡成功后可发送通知,但不计入 streak 统计。
不复杂但容易忽略:所有时间相关判断统一用服务端日期,禁用客户端传的时间;高并发场景下可用 Redis 锁(如 SETNX sign:uid:2024-06-15 1 EX 30)防止重复写入。


















