本文目录导读:

为 PHP 项目实现内容点赞统计,通常需要结合数据库来持久化存储数据,并辅以 AJAX 实现无刷新交互,为了防刷和用户体验,还需要加入用户身份验证和防重复点赞机制。
以下是实现该功能的标准流程和代码示例。
核心设计思路
- 数据结构:需要两张核心表(
post或article表用于存总数,like表用于存明细)。 - 防重复:记录“谁”(用户IP 或 用户ID)对“什么内容”点赞过。
- 状态切换:通常设计为“点赞/取消点赞”(Toggle)。
- 前端交互:使用 JavaScript (Fetch/AJAX) 异步请求 PHP 接口。
第一步:数据库设计 (MySQL)
方案 A:单表存总数 + 明细表(推荐,防止并发问题)
表1:posts (文章/内容表)
CREATE TABLE `posts` ( `id` INT PRIMARY KEY AUTO_INCREMENT, VARCHAR(255) NOT NULL, `content` TEXT, `like_count` INT DEFAULT 0 -- 冗余字段:点赞总数,避免每次都COUNT );
表2:likes (点赞记录表)
CREATE TABLE `likes` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `target_type` VARCHAR(20) NOT NULL, -- 标识类型:'post', 'comment', 'article' `target_id` INT NOT NULL, -- 对应内容的ID `user_id` INT NOT NULL, -- 对应用户表ID(如果是未登录用户可以用IP/DeviceID) `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 关键:防止同一个人对同一个内容多次点赞 UNIQUE KEY `uk_user_target` (`target_type`, `target_id`, `user_id`) );
方案 B:只用一张表(小规模项目)
如果不想维护冗余字段,每次显示时都用 SELECT COUNT(*) FROM likes WHERE target_type='post' AND target_id = ?,优点是简单,缺点是高并发时数据库压力大。
第二步:后端 PHP 实现(核心逻辑)
创建一个 like_api.php 文件处理 AJAX 请求。
<?php
// like_api.php
session_start();
header('Content-Type: application/json');
// 1. 用户登录检测(简化示例,实际项目中请使用完善的认证)
$userId = $_SESSION['user_id'] ?? 0;
if (!$userId) {
echo json_encode(['code' => 0, 'msg' => '请先登录']);
exit;
}
// 2. 获取前端参数
$targetId = intval($_POST['target_id'] ?? 0);
$targetType = 'post'; // 固定为文章类型,可根据项目扩展
if ($targetId <= 0) {
echo json_encode(['code' => 0, 'msg' => '参数错误']);
exit;
}
// 3. 数据库连接(请替换为自己的配置)
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', 'root', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
try {
// 4. 尝试插入点赞记录 (这一步就包含了防重复逻辑,因为 UNIQUE KEY)
$stmt = $pdo->prepare(
"INSERT IGNORE INTO likes (target_type, target_id, user_id, created_at)
VALUES (:type, :id, :uid, NOW())"
);
$stmt->execute([
':type' => $targetType,
':id' => $targetId,
':uid' => $userId
]);
$insertedRows = $stmt->rowCount();
if ($insertedRows > 0) {
// 5. 点赞成功,更新帖子总计数 (使用原子操作防止并发)
$pdo->prepare(
"UPDATE posts SET like_count = like_count + 1 WHERE id = :id"
)->execute([':id' => $targetId]);
$action = 'liked';
} else {
// 6. 如果返回0行,说明已经点过赞了,执行取消点赞操作
$pdo->prepare(
"DELETE FROM likes WHERE target_type = :type AND target_id = :id AND user_id = :uid"
)->execute([
':type' => $targetType,
':id' => $targetId,
':uid' => $userId
]);
// 更新帖子总计数 (减1)
$pdo->prepare(
"UPDATE posts SET like_count = like_count - 1 WHERE id = :id AND like_count > 0"
)->execute([':id' => $targetId]);
$action = 'unliked';
}
// 7. 获取最新点赞数
$stmt = $pdo->prepare("SELECT like_count FROM posts WHERE id = :id");
$stmt->execute([':id' => $targetId]);
$likeCount = $stmt->fetchColumn();
// 8. 返回结果给前端
echo json_encode([
'code' => 1,
'action' => $action,
'like_count' => $likeCount
]);
} catch (PDOException $e) {
// 记录错误日志
error_log($e->getMessage());
echo json_encode(['code' => 0, 'msg' => '服务器繁忙,请稍后重试']);
}
关键点解释:
INSERT IGNORE:UNIQUE KEY 冲突,不会报错,而是返回影响行数为 0,这是区分“点赞”和“取消点赞”的巧妙方法。UPDATE ... like_count + 1:使用数据库的原子操作,避免在高并发下读取+写入的竞态条件(Race Condition)。AND like_count > 0:防止因为并发错误导致点赞数变成负数。
第三步:前端 HTML + JavaScript 实现
<!DOCTYPE html>
<html>
<head>点赞示例</title>
<style>
.like-btn {
cursor: pointer;
display: inline-flex;
align-items: center;
padding: 8px 16px;
background: #f0f0f0;
border: 1px solid #ddd;
border-radius: 20px;
transition: all 0.2s;
}
.like-btn.liked {
background: #ffeef0;
border-color: #ff6b81;
color: #ff4757;
}
.like-btn .heart {
margin-right: 5px;
}
</style>
</head>
<body>
<div class="post" data-post-id="1">
<h2>这是一篇优秀的文章</h2>
<p>内容内容内容...</p>
<!-- 点赞按钮 -->
<button class="like-btn" data-target-id="1">
<span class="heart">♡</span> <!-- 空心心形 -->
<span class="count">0</span> 赞
</button>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 获取所有点赞按钮(项目中可以改为更具体的选择器)
document.querySelectorAll('.like-btn').forEach(btn => {
btn.addEventListener('click', function() {
const targetId = this.dataset.targetId;
const countSpan = this.querySelector('.count');
const heartSpan = this.querySelector('.heart');
const that = this;
// 发送AJAX请求
fetch('like_api.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `target_id=${targetId}`
})
.then(response => response.json())
.then(data => {
if (data.code === 1) {
// 更新显示
countSpan.textContent = data.like_count;
if (data.action === 'liked') {
// 切换为高亮状态
that.classList.add('liked');
heartSpan.innerHTML = '❤'; // 实心心形
} else {
// 切换为未点赞状态
that.classList.remove('liked');
heartSpan.innerHTML = '♡'; // 空心心形
}
} else {
alert(data.msg); // 显示错误信息(如未登录)
}
})
.catch(error => {
console.error('点赞失败:', error);
alert('网络错误,请稍后重试');
});
});
});
});
</script>
</body>
</html>
第四步:显示初始状态(重要)
当页面加载时,你需要告诉前端当前用户是否已经点过赞,以及当前的点赞数。
方法: 修改你的文章模板,在渲染时查询 likes 表。
// 假设在文章列表渲染时
$userId = $_SESSION['user_id'] ?? 0;
$postId = 1; // 循环中的每个文章ID
// 查询点赞数
$stmt = $pdo->prepare("SELECT like_count FROM posts WHERE id = ?");
$stmt->execute([$postId]);
$likeCount = $stmt->fetchColumn();
// 查询当前用户是否点过赞
$stmt = $pdo->prepare("SELECT COUNT(*) FROM likes WHERE target_type='post' AND target_id = ? AND user_id = ?");
$stmt->execute([$postId, $userId]);
$hasLiked = $stmt->fetchColumn() > 0;
// 输出到HTML
echo '<button class="like-btn ' . ($hasLiked ? 'liked' : '') . '" data-target-id="' . $postId . '">';
echo '<span class="heart">' . ($hasLiked ? '❤' : '♡') . '</span>';
echo '<span class="count">' . $likeCount . '</span> 赞';
echo '</button>';
性能与防刷优化建议
-
频率限制(Rate Limiting):
- 在 PHP 端使用 Session 或 Redis 记录用户操作时间戳。
- 同一用户每秒只能点赞一次。
// 简单示例 $lastLikeTime = $_SESSION['last_like_time'] ?? 0; if (time() - $lastLikeTime < 1) { // 限制1秒 echo json_encode(['code' => 0, 'msg' => '操作过于频繁']); exit; } $_SESSION['last_like_time'] = time();
-
使用缓存:
- 对于显示点赞数,可以使用 Memcached 或 Redis 缓存
like_count,定时同步到 MySQL。 - 使用
Redis SADD存储用户ID集合来判断是否点赞,速度极快。
- 对于显示点赞数,可以使用 Memcached 或 Redis 缓存
-
异步处理:
对于高并发场景,点赞请求可以写入消息队列(如 RabbitMQ),然后由后台进程批量更新数据库。
-
安全:
- 校验
target_id是否真实存在,防止恶意刷无效ID。 - 使用 CSRF Token 防止跨站请求伪造(如果涉及登录态)。
- 校验
点赞统计的核心:
- 数据库设计:
details(谁在哪点赞)+sum(总数,可冗余)。 - 并发控制:
UNIQUE KEY+原子 UPDATE。 - 交互:
JavaScript Fetch+PHP 接口+Toggle 逻辑。 - 状态持久化:页面加载时从后端获取当前用户的点赞状态。
版权声明: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直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播