本文目录导读:

我来给你详细讲解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>
注意事项
- 唯一约束:利用数据库的唯一约束防止重复签到
- 事务处理:使用事务确保数据一致性
- 性能优化:为 user_id 和 sign_date 创建联合索引
- 缓存考虑:高频查询可使用 Redis 缓存
- 跨时区:如果用户跨时区,需要注意日期处理逻辑
这种实现方式简单高效,能够准确计算连续签到天数,并且易于维护和扩展。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播