本文目录导读:

在PHP项目中实现资讯收藏功能,核心业务逻辑通常是:用户对自己感兴趣的资讯文章进行标记(收藏/取消收藏),并在个人中心查看已收藏列表。
下面我会从数据库设计、后端API逻辑、前端交互 三个主要方面,结合代码示例,为你梳理一个标准且可靠的实现方案。
数据库设计
收藏功能通常是多对多关系(一个用户可以收藏多篇文章,一篇文章可以被多个用户收藏),建议设计一张独立的favorites表。
-- 收藏表
CREATE TABLE `favorites` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL COMMENT '用户ID',
`article_id` int(11) NOT NULL COMMENT '资讯文章ID',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '收藏时间',
PRIMARY KEY (`id`),
-- 关键:建立唯一索引,防止重复收藏
UNIQUE KEY `uk_user_article` (`user_id`, `article_id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户收藏表';
-- 假设已有的资讯文章表 (用于关联查询)
CREATE TABLE `articles` (
`id` int(11) NOT NULL AUTO_INCREMENT, varchar(255) NOT NULL,
`content` text,
`published_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
设计要点:
UNIQUE KEY uk_user_article (user_id, article_id):这是防止重复收藏的关键,依赖数据库约束比每次操作前都查询一次更可靠。created_at:记录收藏时间,便于按时间排序展示列表。
后端API逻辑 (PHP示例)
这里以常见的MVC框架(如ThinkPHP、Laravel)或原生代码为例,核心逻辑是通用的,假设你使用PDO操作数据库。
切换收藏状态 (Toggle) 接口
最常用的方式是:如果已经收藏,则取消收藏;如果未收藏,则添加收藏。
<?php
// 示例: toggleFavorite.php
// 假设已经通过 session/jwt 获取到了当前用户ID
$userId = $_SESSION['user_id'] ?? 0;
$articleId = intval($_POST['article_id'] ?? 0);
if ($userId <= 0 || $articleId <= 0) {
echo json_encode(['code' => 0, 'msg' => '参数错误或未登录']);
exit;
}
// 1. 连接数据库 (使用PDO示例)
$pdo = new PDO('mysql:host=localhost;dbname=your_db;charset=utf8mb4', 'root', 'password');
// 2. 检查该用户是否已收藏该文章
$stmt = $pdo->prepare("SELECT id FROM favorites WHERE user_id = ? AND article_id = ?");
$stmt->execute([$userId, $articleId]);
$favorite = $stmt->fetch();
if ($favorite) {
// 3. 如果已存在,则执行删除(取消收藏)
$deleteStmt = $pdo->prepare("DELETE FROM favorites WHERE user_id = ? AND article_id = ?");
$deleteStmt->execute([$userId, $articleId]);
$isFavorited = false;
$msg = '取消收藏成功';
} else {
// 4. 如果不存在,则执行插入(添加收藏)
$insertStmt = $pdo->prepare("INSERT INTO favorites (user_id, article_id) VALUES (?, ?)");
$insertStmt->execute([$userId, $articleId]);
$isFavorited = true;
$msg = '收藏成功';
}
// 5. 返回JSON结果给前端
echo json_encode([
'code' => 1,
'msg' => $msg,
'data' => [
'is_favorited' => $isFavorited // 前端可以根据这个值切换按钮样式
]
]);
获取用户收藏列表
<?php
// 示例: getFavorites.php
$userId = $_SESSION['user_id'] ?? 0;
$page = intval($_GET['page'] ?? 1);
$pageSize = 10;
$offset = ($page - 1) * $pageSize;
if ($userId <= 0) {
echo json_encode(['code' => 0, 'msg' => '未登录']);
exit;
}
$pdo = new PDO('mysql:host=localhost;dbname=your_db;charset=utf8mb4', 'root', 'password');
// 连表查询:根据收藏表关联文章表,获取文章标题、时间等信息
$sql = "SELECT a.id, a.title, a.published_at, f.created_at as favorite_time
FROM favorites f
INNER JOIN articles a ON f.article_id = a.id
WHERE f.user_id = ?
ORDER BY f.created_at DESC
LIMIT ? OFFSET ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$userId, $pageSize, $offset]);
$favorites = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 可选:获取总条数用于分页
$countStmt = $pdo->prepare("SELECT COUNT(*) FROM favorites WHERE user_id = ?");
$countStmt->execute([$userId]);
$total = $countStmt->fetchColumn();
echo json_encode([
'code' => 1,
'data' => [
'list' => $favorites,
'total' => $total,
'page' => $page
]
]);
检查单篇文章的收藏状态(辅助接口)
通常在文章详情页,需要判断当前用户是否收藏了这篇文章,用于初始化收藏按钮的状态。
<?php
// 示例: checkFavorite.php
$userId = $_SESSION['user_id'] ?? 0;
$articleId = intval($_GET['article_id'] ?? 0);
$pdo = new PDO('...');
$stmt = $pdo->prepare("SELECT id FROM favorites WHERE user_id = ? AND article_id = ?");
$stmt->execute([$userId, $articleId]);
$isFavorited = (bool)$stmt->fetch();
echo json_encode(['code' => 1, 'data' => ['is_favorited' => $isFavorited]]);
前端交互 (JavaScript + HTML示例)
假设用户点击收藏按钮时触发切换操作。
HTML部分
<!-- 文章详情页 -->
<button id="favoriteBtn" data-article-id="123">
<span id="favIcon">☆</span>
<span id="favText">收藏</span>
</button>
JavaScript部分 (使用Fetch API)
const favoriteBtn = document.getElementById('favoriteBtn');
const articleId = favoriteBtn.dataset.articleId;
// 1. 页面加载时,检查该文章是否已被用户收藏
fetch(`checkFavorite.php?article_id=${articleId}`)
.then(res => res.json())
.then(data => {
if (data.code === 1 && data.data.is_favorited) {
updateFavoriteBtn(true); // 更新按钮为已收藏状态
}
});
// 2. 绑定点击事件,触发切换
favoriteBtn.addEventListener('click', function() {
const formData = new FormData();
formData.append('article_id', articleId);
fetch('toggleFavorite.php', {
method: 'POST',
body: formData
})
.then(res => res.json())
.then(data => {
if (data.code === 1) {
// 根据返回的 is_favorited 状态更新UI
updateFavoriteBtn(data.data.is_favorited);
// 可以显示一个提示消息
alert(data.msg);
} else {
alert(data.msg || '操作失败,请重试');
// 如果是未登录,会跳转登录页
}
})
.catch(err => {
console.error('Error:', err);
alert('网络错误');
});
});
// 辅助函数:更新按钮样式和文本
function updateFavoriteBtn(isFavorited) {
const icon = document.getElementById('favIcon');
const text = document.getElementById('favText');
if (isFavorited) {
icon.innerHTML = '★'; // 实心星
text.innerHTML = '已收藏';
favoriteBtn.classList.add('active'); // 添加高亮样式
} else {
icon.innerHTML = '☆'; // 空心星
text.innerHTML = '收藏';
favoriteBtn.classList.remove('active');
}
}
性能优化与注意事项
-
防重复点击:前端在点击后应立即禁用按钮,等待接口返回后再恢复,防止用户快速点击发送多个请求。
// 在点击事件开始时禁用按钮 favoriteBtn.disabled = true; fetch(...).then(...).finally(() => { favoriteBtn.disabled = false; }); -
用户ID安全:永远不要从客户端获取
user_id,必须在服务端通过Session或JWT验证。 -
缓存与计数:如果文章需要显示“收藏数”,可以在文章表
articles中加一个favorites_count字段,每次用户收藏/取消收藏时,通过事务更新该字段,但要注意并发问题,可以使用UPDATE articles SET favorites_count = favorites_count + 1 WHERE id = ?这种原子操作。 -
大表处理:如果用户量非常大(百万级),可以考虑使用Redis的SortedSet来存储某个用户的所有收藏ID,查询列表时走缓存,但写入时双写数据库和缓存,并处理好一致性。
-
安全性:在
toggleFavorite接口中,最好验证article_id对应的文章是否存在,防止用户收藏不存在的文章。
实现收藏功能的关键点是:
- 数据库:建立
(user_id, article_id)的唯一联合索引,防止重复。 - 后端:提供切换接口(增删一体)和列表查询接口。
- 前端:处理好按钮状态的切换(点击后立即禁用防止重复请求),并根据接口返回的
is_favorited真实状态更新UI。
按照这个标准方案实现,收藏功能就可以稳定、高效地运行了。