# PHP如何实现点赞取消功能 ## 目录 1. [功能需求分析](#功能需求分析) 2. [数据库设计](#数据库设计) 3. [前端交互实现](#前端交互实现) 4. [后端逻辑处理](#后端逻辑处理) - [4.1 点赞功能实现](#41-点赞功能实现) - [4.2 取消点赞实现](#42-取消点赞实现) 5. [性能优化方案](#性能优化方案) 6. [安全防护措施](#安全防护措施) 7. [完整代码示例](#完整代码示例) 8. [扩展功能建议](#扩展功能建议) ## 功能需求分析 点赞/取消点赞是社交类网站的基础功能,需要满足以下核心需求: 1. 用户可对内容(文章/视频/评论)进行点赞 2. 已点赞用户可取消操作 3. 实时显示点赞数量变化 4. 防止重复点赞和恶意刷赞 5. 区分已点赞/未点赞状态(UI反馈) 技术实现要点: - 前端通过AJAX实现无刷新交互 - 后端使用PHP+MySQL处理数据 - 需要用户认证系统支持 ## 数据库设计 ### 方案一:计数器+关系表 ```sql -- 内容主表(示例) CREATE TABLE `posts` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(255) NOT NULL, `content` text NOT NULL, `like_count` int(11) DEFAULT 0, PRIMARY KEY (`id`) ); -- 点赞关系表 CREATE TABLE `user_likes` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL, `post_id` int(11) NOT NULL, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `user_post` (`user_id`,`post_id`) );
-- 不维护计数器,每次实时统计 CREATE TABLE `user_likes` ( `user_id` int(11) NOT NULL, `post_id` int(11) NOT NULL, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`user_id`,`post_id`) );
两种方案对比:
方案 | 写入性能 | 读取性能 | 数据一致性 |
---|---|---|---|
计数器+关系表 | 较高(需事务) | 极高 | 需要维护 |
纯关系表 | 极高 | 较低(需COUNT) | 自动保证 |
<div class="post" data-post-id="123"> <h2>文章标题</h2> <div class="like-area"> <button class="like-btn <?= $hasLiked ? 'active' : '' ?>"> <i class="icon-thumbs-up"></i> <span class="like-count"><?= $likeCount ?></span> </button> </div> </div>
document.querySelectorAll('.like-btn').forEach(btn => { btn.addEventListener('click', async function() { const postId = this.closest('.post').dataset.postId; const isActive = this.classList.contains('active'); try { const response = await fetch('/like.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ post_id: postId, action: isActive ? 'unlike' : 'like' }) }); const result = await response.json(); if (result.success) { this.classList.toggle('active'); this.querySelector('.like-count').textContent = result.like_count; } } catch (error) { console.error('操作失败:', error); } }); });
// like.php require 'db_connect.php'; session_start(); if (!isset($_SESSION['user_id'])) { http_response_code(401); die(json_encode(['error' => '请先登录'])); } $input = json_decode(file_get_contents('php://input'), true); $postId = intval($input['post_id'] ?? 0); $action = $input['action'] === 'unlike' ? 'unlike' : 'like'; // 开启事务 $pdo->beginTransaction(); try { if ($action === 'like') { // 检查是否已点赞 $stmt = $pdo->prepare("SELECT 1 FROM user_likes WHERE user_id = ? AND post_id = ?"); $stmt->execute([$_SESSION['user_id'], $postId]); if ($stmt->fetch()) { throw new Exception('您已经点过赞了'); } // 插入点赞记录 $pdo->prepare("INSERT INTO user_likes (user_id, post_id) VALUES (?, ?)") ->execute([$_SESSION['user_id'], $postId]); // 更新计数器 $pdo->prepare("UPDATE posts SET like_count = like_count + 1 WHERE id = ?") ->execute([$postId]); } else { // 取消点赞逻辑... } // 获取最新点赞数 $likeCount = $pdo->query("SELECT like_count FROM posts WHERE id = $postId") ->fetchColumn(); $pdo->commit(); echo json_encode([ 'success' => true, 'like_count' => $likeCount ]); } catch (Exception $e) { $pdo->rollBack(); http_response_code(400); echo json_encode(['error' => $e->getMessage()]); }
// 接续上面的try块中的else分支 } else { // 检查是否有点赞记录 $stmt = $pdo->prepare("SELECT 1 FROM user_likes WHERE user_id = ? AND post_id = ?"); $stmt->execute([$_SESSION['user_id'], $postId]); if (!$stmt->fetch()) { throw new Exception('您尚未点赞'); } // 删除点赞记录 $pdo->prepare("DELETE FROM user_likes WHERE user_id = ? AND post_id = ?") ->execute([$_SESSION['user_id'], $postId]); // 更新计数器 $pdo->prepare("UPDATE posts SET like_count = GREATEST(0, like_count - 1) WHERE id = ?") ->execute([$postId]); }
\(cacheKey = "post:{\)postId}:likes”;
// 写入时更新缓存 \(redis->set(\)cacheKey, $likeCount, 3600);
// 读取时优先查缓存 \(likeCount = \)redis->get(\(cacheKey); if (\)likeCount === false) { \(likeCount = \)pdo->query(“SELECT like_count…”)->fetchColumn(); \(redis->set(\)cacheKey, $likeCount, 3600); }
2. **批量查询优化**: ```sql -- 一次查询获取用户是否点赞过多个文章 SELECT post_id FROM user_likes WHERE user_id = ? AND post_id IN (1, 2, 3, 4);
// 对于高流量场景,可采用队列异步更新 $queue->push([ 'type' => 'like', 'user_id' => $userId, 'post_id' => $postId, 'action' => $action ]);
CSRF防护:
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
频率限制: “`php \(redis->incr("user:{\)_SESSION[‘user_id’]}:like_attempts”); \(redis->expire("user:{\)_SESSION[‘user_id’]}:like_attempts”, 60);
if ($redis->get() > 30) { http_response_code(429); die(‘操作过于频繁’); }
3. **输入验证增强**: ```php if (!ctype_digit($postId) { die('非法参数'); } // 检查文章是否存在 $stmt = $pdo->prepare("SELECT 1 FROM posts WHERE id = ?"); $stmt->execute([$postId]); if (!$stmt->fetch()) { die('内容不存在'); }
<?php $host = 'localhost'; $dbname = 'your_database'; $user = 'db_user'; $pass = 'db_password'; try { $pdo = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } catch (PDOException $e) { die("数据库连接失败: " . $e->getMessage()); }
<?php session_start(); require 'db_connect.php'; // 获取文章列表 $posts = $pdo->query("SELECT id, title, content, like_count FROM posts")->fetchAll(); // 检查用户点赞状态 $userLikes = []; if (isset($_SESSION['user_id'])) { $stmt = $pdo->prepare("SELECT post_id FROM user_likes WHERE user_id = ?"); $stmt->execute([$_SESSION['user_id']]); $userLikes = $stmt->fetchAll(PDO::FETCH_COLUMN); } ?> <!DOCTYPE html> <html> <!-- 头部内容... --> <body> <?php foreach ($posts as $post): ?> <div class="post" data-post-id="<?= $post['id'] ?>"> <h2><?= htmlspecialchars($post['title']) ?></h2> <p><?= nl2br(htmlspecialchars($post['content'])) ?></p> <button class="like-btn <?= in_array($post['id'], $userLikes) ? 'active' : '' ?>"> <i class="icon-thumbs-up"></i> <span class="like-count"><?= $post['like_count'] ?></span> </button> </div> <?php endforeach; ?> <script src="like.js"></script> </body> </html>
点赞通知系统:
// 在点赞成功后 if ($action === 'like') { $stmt = $pdo->prepare("INSERT INTO notifications (user_id, type, content_id, sender_id) VALUES (?, 'like', ?, ?)"); $authorId = $pdo->query("SELECT user_id FROM posts WHERE id = $postId") ->fetchColumn(); $stmt->execute([$authorId, $postId, $_SESSION['user_id']]); }
点赞排行榜:
SELECT posts.*, COUNT(user_likes.id) as like_count FROM posts LEFT JOIN user_likes ON posts.id = user_likes.post_id GROUP BY posts.id ORDER BY like_count DESC LIMIT 10
点赞动画效果:
btn.classList.add('animate'); setTimeout(() => { btn.classList.remove('animate'); }, 1000);
多类型内容支持:
ALTER TABLE user_likes ADD COLUMN content_type ENUM('post','comment','video') NOT NULL;
通过以上实现,我们完成了一个健壮的点赞/取消点赞系统。实际项目中可根据具体需求进行调整和扩展。 “`
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。