您好,登录后才能下订单哦!
# 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进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。