# 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進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。