PHP签到功能怎么连续算

wen PHP项目 4

本文目录导读:

PHP签到功能怎么连续算

  1. 数据库表设计
  2. PHP核心签到逻辑
  3. 使用示例
  4. 前端展示代码
  5. 注意事项

我来给你详细讲解PHP签到功能中连续签到计算的实现方法。

数据库表设计

首先创建签到记录表:

CREATE TABLE `sign_records` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL COMMENT '用户ID',
  `sign_date` date NOT NULL COMMENT '签到日期',
  `sign_time` datetime NOT NULL COMMENT '签到时间',
  `continuous_days` int(11) DEFAULT 0 COMMENT '连续签到天数',
  PRIMARY KEY (`id`),
  UNIQUE KEY `user_date_unique` (`user_id`, `sign_date`),
  KEY `user_id_index` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

PHP核心签到逻辑

<?php
class SignService {
    private $pdo;
    public function __construct($pdo) {
        $this->pdo = $pdo;
    }
    /**
     * 执行签到
     * @param int $userId 用户ID
     * @return array 签到结果
     */
    public function sign($userId) {
        $today = date('Y-m-d');
        $yesterday = date('Y-m-d', strtotime('-1 day'));
        // 检查今天是否已签到
        if ($this->isSigned($userId, $today)) {
            return ['code' => 0, 'msg' => '今天已经签到过了'];
        }
        try {
            $this->pdo->beginTransaction();
            // 获取昨天是否签到
            $yesterdaySigned = $this->isSigned($userId, $yesterday);
            // 获取昨天的连续签到天数
            $continuousDays = 0;
            if ($yesterdaySigned) {
                $continuousDays = $this->getContinuousDays($userId, $yesterday);
            }
            // 计算新的连续天数
            $newContinuousDays = $yesterdaySigned ? ($continuousDays + 1) : 1;
            // 插入签到记录
            $sql = "INSERT INTO sign_records 
                    (user_id, sign_date, sign_time, continuous_days) 
                    VALUES (?, ?, NOW(), ?)";
            $stmt = $this->pdo->prepare($sql);
            $stmt->execute([$userId, $today, $newContinuousDays]);
            $this->pdo->commit();
            return [
                'code' => 1, 
                'msg' => '签到成功',
                'continuous_days' => $newContinuousDays
            ];
        } catch (Exception $e) {
            $this->pdo->rollBack();
            return ['code' => 0, 'msg' => '签到失败:' . $e->getMessage()];
        }
    }
    /**
     * 检查某天是否已签到
     */
    public function isSigned($userId, $date) {
        $sql = "SELECT COUNT(*) FROM sign_records 
                WHERE user_id = ? AND sign_date = ?";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([$userId, $date]);
        return $stmt->fetchColumn() > 0;
    }
    /**
     * 获取某天的连续签到天数
     */
    public function getContinuousDays($userId, $date) {
        $sql = "SELECT continuous_days FROM sign_records 
                WHERE user_id = ? AND sign_date = ?";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([$userId, $date]);
        return (int)$stmt->fetchColumn();
    }
    /**
     * 获取用户签到日历
     * @param int $userId 用户ID
     * @param string $month 月份(2024-01)
     */
    public function getSignCalendar($userId, $month) {
        $startDate = $month . '-01';
        $endDate = date('Y-m-t', strtotime($startDate));
        $sql = "SELECT sign_date, continuous_days 
                FROM sign_records 
                WHERE user_id = ? AND sign_date BETWEEN ? AND ?
                ORDER BY sign_date ASC";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([$userId, $startDate, $endDate]);
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
    /**
     * 计算连续签到(使用日期反向遍历)
     * @param int $userId 用户ID
     * @param date $date 目标日期
     */
    public function calcContinuousDays($userId, $date = null) {
        $date = $date ?: date('Y-m-d');
        $continuousDays = 0;
        $currentDate = $date;
        // 如果今天没签到,从昨天开始计算
        if (!$this->isSigned($userId, $date)) {
            $currentDate = date('Y-m-d', strtotime($date . ' -1 day'));
        }
        // 循环向前统计连续签到天数
        while ($this->isSigned($userId, $currentDate)) {
            $continuousDays++;
            $currentDate = date('Y-m-d', strtotime($currentDate . ' -1 day'));
        }
        return $continuousDays;
    }
    /**
     * 使用SQL直接计算连续天数(效率更高)
     */
    public function calcContinuousDaysBySQL($userId, $date = null) {
        $date = $date ?: date('Y-m-d');
        // 方法:找到最近的无签到日期,然后计算差值
        $sql = "SELECT MAX(sign_date) as last_sign_date 
                FROM sign_records 
                WHERE user_id = ? AND sign_date <= ?
                AND sign_date NOT IN (
                    SELECT DATE_ADD(sign_date, INTERVAL 1 DAY)
                    FROM sign_records
                    WHERE user_id = ? AND sign_date < ?
                )";
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute([$userId, $date, $userId, $date]);
        $result = $stmt->fetch();
        if ($result && $result['last_sign_date']) {
            // 如果今天已签到,从今天开始计算;否则从昨天开始
            $startDate = $this->isSigned($userId, $date) ? $date : 
                        ($this->isSigned($userId, date('Y-m-d', strtotime($date . ' -1 day'))) ? 
                        date('Y-m-d', strtotime($date . ' -1 day')) : null);
            if ($startDate) {
                return (int)((strtotime($startDate) - strtotime($result['last_sign_date'])) / 86400) + 1;
            }
        }
        return 0;
    }
    /**
     * 获取签到奖励(根据连续天数)
     */
    public function getSignRewards($continuousDays) {
        $rewards = [
            1 => ['points' => 1, 'desc' => '第一天'],
            2 => ['points' => 2, 'desc' => '第二天'],
            3 => ['points' => 3, 'desc' => '第三天'],
            4 => ['points' => 4, 'desc' => '第四天'],
            5 => ['points' => 5, 'desc' => '第五天'],
            6 => ['points' => 6, 'desc' => '第六天'],
        ];
        // 超过7天,每7天为一个周期
        if ($continuousDays > 6) {
            $cycle = floor(($continuousDays - 1) / 7);
            $dayInCycle = ($continuousDays - 1) % 7 + 1;
            if ($dayInCycle == 7) {
                return ['points' => 10 + $cycle * 2, 'desc' => "连续签到{$continuousDays}天,额外奖励"];
            }
            return $rewards[$dayInCycle];
        }
        return $rewards[$continuousDays];
    }
}

使用示例

<?php
// 初始化
$pdo = new PDO('mysql:host=localhost;dbname=test', 'username', 'password');
$signService = new SignService($pdo);
// 用户签到
$userId = 1;
$result = $signService->sign($userId);
if ($result['code'] == 1) {
    echo "签到成功!连续签到" . $result['continuous_days'] . "天";
    // 获取奖励
    $rewards = $signService->getSignRewards($result['continuous_days']);
    echo "获得奖励:" . $rewards['points'] . "积分";
} else {
    echo $result['msg'];
}
// 查看用户当月签到情况
$signCalendar = $signService->getSignCalendar($userId, '2024-01');
echo "本月签到:";
foreach ($signCalendar as $record) {
    echo $record['sign_date'] . " ";
}
// 简单的连续天数计算(不使用缓存字段)
$continuous = $signService->calcContinuousDays($userId);
echo "当前连续签到:{$continuous}天";
// 获取签到状态
function getSignStatus() {
    $userId = $_SESSION['user_id'];
    $signService = new SignService($pdo);
    $today = date('Y-m-d');
    $isSigned = $signService->isSigned($userId, $today);
    $todayContinuousDays = $isSigned ? 
        $signService->getContinuousDays($userId, $today) :
        $signService->calcContinuousDays($userId);
    return [
        'is_signed' => $isSigned,
        'continuous_days' => $todayContinuousDays
    ];
}
?>

前端展示代码

<!-- 签到按钮 -->
<div class="sign-container">
    <button id="signBtn" onclick="doSign()">
        <?php if ($isTodaySigned): ?>
            今日已签到
        <?php else: ?>
            签到
        <?php endif; ?>
    </button>
    <div id="signResult"></div>
</div>
<script>
function doSign() {
    fetch('/api/sign.php', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            user_id: <?php echo $userId; ?>
        })
    })
    .then(response => response.json())
    .then(data => {
        if (data.code == 1) {
            document.getElementById('signResult').innerHTML = 
                '签到成功!连续签到' + data.continuous_days + '天';
        } else {
            alert(data.msg);
        }
    });
}
</script>

注意事项

  1. 唯一约束:利用数据库的唯一约束防止重复签到
  2. 事务处理:使用事务确保数据一致性
  3. 性能优化:为 user_id 和 sign_date 创建联合索引
  4. 缓存考虑:高频查询可使用 Redis 缓存
  5. 跨时区:如果用户跨时区,需要注意日期处理逻辑

这种实现方式简单高效,能够准确计算连续签到天数,并且易于维护和扩展。

抱歉,评论功能暂时关闭!