前後端分離架構下,點贊功能典型流程:前端攔截點擊 → Ajax 請求後端 → 後端驗證 Cookie/Session → 更新資料庫 → 返回新計數 → 前端更新顯示。本文提供完整可運行代碼。
CREATE TABLE `good` (
`id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
`value` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '贊數'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `good` (`id`, `value`) VALUES (1,0),(2,0),(3,0),(4,0);
<?php
header('Content-Type: application/json; charset=utf-8');
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', 'root', '', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$action = $_GET['action'] ?? 'get'; // get | incr
$id = (int)($_GET['id'] ?? 0);
$cookieKey = "like_{$id}";
if ($action === 'get') {
$stmt = $pdo->prepare('SELECT value FROM `good` WHERE id=?');
$stmt->execute([$id]);
echo json_encode(['count' => (int)($stmt->fetchColumn() ?? 0)]);
return;
}
if ($action === 'incr') {
// 簡易防重:Cookie 存儲 (生產環境建議 Redis + 用戶ID)
if (isset($_COOKIE[$cookieKey])) {
http_response_code(409);
echo json_encode(['error' => 'Already liked']);
return;
}
$pdo->beginTransaction();
try {
$stmt = $pdo->prepare('UPDATE `good` SET value = value + 1 WHERE id=?');
$stmt->execute([$id]);
$stmt = $pdo->prepare('SELECT value FROM `good` WHERE id=?');
$stmt->execute([$id]);
$newCount = (int)$stmt->fetchColumn();
$pdo->commit();
// 設定 Cookie 1 年
setcookie($cookieKey, '1', time() + 3600*24*365, '/', '', false, true);
echo json_encode(['count' => $newCount]);
} catch (Exception $e) {
$pdo->rollBack();
http_response_code(500);
echo json_encode(['error' => 'Server error']);
}
return;
}
http_response_code(400);
echo json_encode(['error' => 'Invalid action']);
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>點贊 Demo</title></head><body>
<div id="likes">
<span data-id="1">0</span> <button data-id="1">贊+1</button><br>
<span data-id="2">0</span> <button data-id="2">贊+1</button><br>
<span data-id="3">0</span> <button data-id="3">贊+1</button><br>
<span data-id="4">0</span> <button data-id="4">贊+1</button>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// 初始載入
document.querySelectorAll('#likes span[data-id]').forEach(loadCount);
// 綁定點擊
document.querySelectorAll('#likes button[data-id]').forEach(btn => {
btn.addEventListener('click', () => increment(btn.dataset.id));
});
});
function loadCount(span) {
fetch(`like.php?action=get&id=${span.dataset.id}`)
.then(r => r.json())
.then(d => span.textContent = d.count)
.catch(console.error);
}
function increment(id) {
fetch(`like.php?action=incr&id=${id}`)
.then(r => {
if (r.status === 409) return r.json().then(d => { throw d.error; });
if (!r.ok) throw new Error('Network error');
return r.json();
})
.then(d => {
document.querySelector(`#likes span[data-id="${id}"]`).textContent = d.count;
alert('點贊成功!');
})
.catch(err => alert(err || '已點過贊'));
}
</script>
</body></html>
mysql_* (已棄用)。user_id + item_id 唯一索引 + Redis Set 判斷。GET /like?id=1 讀取、POST /like {id:1} 增加,語義更清晰。INCR 計數 + 定時持久化落庫,抗高併發。user_likes (user_id, item_id, created_at),唯一索引防重,支援取消點贊。