本文目录导读:

在 PHP 项目中实现商品详情点赞功能,核心思路其实很简单:记录哪个用户给哪个商品点过赞,并更新商品的点赞总数,这需要一套前后端配合的流程。
下面我会从数据库设计、后端 PHP 接口、前端交互和防重复点赞这几个方面,给出一个清晰、可落地的实现方案。
数据库设计
你需要两张核心表:
-- 1. 商品表(如果已有,只需添加点赞数字段)
ALTER TABLE `products`
ADD COLUMN `like_count` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '点赞总数';
-- 2. 点赞记录表(核心:记录谁给什么内容点过赞)
CREATE TABLE `product_likes` (
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`user_id` INT UNSIGNED NOT NULL COMMENT '用户ID',
`product_id` INT UNSIGNED NOT NULL COMMENT '商品ID',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- 防止重复点赞的关键:联合唯一索引
UNIQUE KEY `uk_user_product` (`user_id`, `product_id`),
INDEX `idx_product_id` (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
为什么这样设计?
like_count字段在商品表中,方便直接展示,避免每次统计点赞数时都去 COUNT 记录表,性能更好。product_likes表通过 联合唯一索引 从数据库层面杜绝了同一个用户对同一个商品点赞多于一次。
后端 PHP 接口实现
以下是一个典型的场景:用户点击“点赞”按钮,由 JS 发送 AJAX 请求到 PHP 后端。
接口文件:/api/toggle_like.php
<?php
// 1. 开启会话,获取登录用户信息
session_start();
// 假设用户登录后,用户ID存储在 SESSION 中
$user_id = $_SESSION['user_id'] ?? 0;
$product_id = (int)($_POST['product_id'] ?? 0);
if ($user_id <= 0 || $product_id <= 0) {
echo json_encode(['code' => -1, 'msg' => '参数错误或未登录']);
exit;
}
// 2. 连接数据库(请替换为你的数据库配置)
$pdo = new PDO('mysql:host=localhost;dbname=your_db;charset=utf8mb4', 'root', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
try {
$pdo->beginTransaction();
// 3. 核心逻辑:查询是否已点赞
$stmt = $pdo->prepare("SELECT id FROM product_likes WHERE user_id = ? AND product_id = ?");
$stmt->execute([$user_id, $product_id]);
$has_liked = $stmt->fetch();
if ($has_liked) {
// **已点赞:执行取消点赞操作**
// 删除点赞记录
$deleteStmt = $pdo->prepare("DELETE FROM product_likes WHERE user_id = ? AND product_id = ?");
$deleteStmt->execute([$user_id, $product_id]);
// 商品点赞数减1
$updateStmt = $pdo->prepare("UPDATE products SET like_count = like_count - 1 WHERE id = ? AND like_count > 0");
$updateStmt->execute([$product_id]);
$action = 'unlike';
} else {
// **未点赞:执行点赞操作**
// 增加点赞记录
$insertStmt = $pdo->prepare("INSERT INTO product_likes (user_id, product_id) VALUES (?, ?)");
$insertStmt->execute([$user_id, $product_id]);
// 商品点赞数加1
$updateStmt = $pdo->prepare("UPDATE products SET like_count = like_count + 1 WHERE id = ?");
$updateStmt->execute([$product_id]);
$action = 'like';
}
// 4. 查询当前最新的点赞总数
$countStmt = $pdo->prepare("SELECT like_count FROM products WHERE id = ?");
$countStmt->execute([$product_id]);
$current_count = $countStmt->fetchColumn();
$pdo->commit();
// 5. 返回 JSON 数据给前端
echo json_encode([
'code' => 1,
'msg' => '操作成功',
'data' => [
'action' => $action, // 'like' 或 'unlike'
'count' => (int)$current_count
]
]);
} catch (Exception $e) {
$pdo->rollBack();
echo json_encode(['code' => -2, 'msg' => '服务器繁忙,请稍后再试']);
}
?>
要点解释:
- 事务:点赞记录和商品点赞数的增减需要在一个事务里,保证数据一致(要么都成功,要么都失败)。
- 防重复:利用
UNIQUE索引 +INSERT的方式(如果是首次),第二次INSERT会失败,这里用SELECT先判断更直观易懂。 - 原子操作:
UPDATE products SET like_count = like_count + 1是原子操作,即使并发高,也不会导致计数错误。
前端实现
以一个简单的 HTML + JS 为例。
<!-- 商品详情页中的点赞按钮 -->
<div class="like-area">
<button id="likeBtn" data-product-id="<?= $product['id'] ?>" class="like-btn <?= $has_liked ? 'active' : '' ?>">
❤️ <span id="likeCount"><?= $product['like_count'] ?></span>
</button>
</div>
<script>
document.getElementById('likeBtn').addEventListener('click', function() {
const btn = this;
const productId = btn.getAttribute('data-product-id');
// 使用 Fetch API 发送请求
fetch('/api/toggle_like.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'product_id=' + productId
})
.then(response => response.json())
.then(data => {
if (data.code === 1) {
// 更新页面显示的点赞数
document.getElementById('likeCount').innerText = data.data.count;
// 切换点赞状态样式(比如高亮)
if (data.data.action === 'like') {
btn.classList.add('active');
} else {
btn.classList.remove('active');
}
} else {
alert(data.msg); // 请先登录”
}
})
.catch(error => {
console.error('Error:', error);
});
});
</script>
关键点:
- 点击按钮后,先发 AJAX 请求给后端,根据后端返回的结果更新界面。不要直接在前端自增减,否则可能和后端数据不同步。
data-product-id把商品 ID 传给 JS。
如何判断用户是否已点赞(预加载状态)
在渲染商品详情页时,PHP 应该查询当前用户是否已经点赞,这样页面加载时就能显示正确的按钮状态(高亮/未高亮)。
// 在商品详情页的 PHP 模块中
$has_liked = false;
if (isset($_SESSION['user_id'])) {
$stmt = $pdo->prepare("SELECT id FROM product_likes WHERE user_id = ? AND product_id = ?");
$stmt->execute([$_SESSION['user_id'], $product_id]);
$has_liked = $stmt->fetch() ? true : false;
}
然后在 HTML 中:
<button id="likeBtn" class="like-btn <?= $has_liked ? 'active' : '' ?>">
一些需要注意的进阶问题
-
性能优化:
- 对于高并发场景,直接操作
like_count字段可能成为瓶颈,可以考虑使用 Redis 缓存点赞数,定期同步到数据库。 - 如果商品点赞操作非常频繁,可以将点赞请求放入 消息队列(如 RabbitMQ、Kafka)进行异步处理。
- 对于高并发场景,直接操作
-
防刷机制:
- 除了利用数据库唯一索引防止重复点赞,还可以在服务端加入 限流 逻辑(同一 IP 每分钟只能对同一商品点赞 5 次)。
- 对接口进行 身份验证(如 JWT 或 Session),防止匿名攻击。
-
用户体验:
- 如果网络有延迟,可以 乐观更新 UI(先变亮并 +1,然后等待后端确认,若失败再回滚),让用户感觉更流畅。
- 加上加载动画或短暂禁用按钮,避免用户快速连点产生多个请求。
-
表设计扩展:
- 如果未来有点踩(dislike)、不同内容类型(文章、评论)等需求,可以在
product_likes表中增加type字段,通过type和target_id来区分。
- 如果未来有点踩(dislike)、不同内容类型(文章、评论)等需求,可以在
点赞功能的核心就是 账号系统 + 记录表 + 原子更新,上面提供的方案虽然是一个基础实现,但已经具备了生产环境可用的核心逻辑(事务、防重复、实时更新),你可以根据项目实际情况,在其基础上增加缓存、限流、异步处理等优化。