PHP项目点赞与收藏功能:从零搭建高效交互模块
目录导读
- 功能设计核心思路
- 数据库表结构优化方案
- 点赞功能的PHP实现(含防刷机制)
- 收藏功能的多态关联设计
- 性能优化与缓存策略
- 常见问题解答(Q&A)
- 总结与扩展建议
功能设计核心思路
在PHP项目中实现点赞与收藏功能,首先需要明确业务逻辑:点赞通常要求一人一次有效点击(可取消),收藏则需区分用户私有集合与公开收藏,设计时应遵循高内聚低耦合原则,将交互逻辑与业务主体解耦。

常见误区:将点赞和收藏数直接存储在内容主表(如文章表)的likes_count字段中,这会导致并发写锁问题,正确做法是通过计数服务表或Redis原子递增实现。
数据库表结构优化方案
点赞表(likes)
CREATE TABLE `likes` ( `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `user_id` INT UNSIGNED NOT NULL, `target_type` VARCHAR(50) NOT NULL COMMENT '模型类型: article/post/comment', `target_id` INT UNSIGNED NOT NULL, `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY `uk_user_target` (`user_id`, `target_type`, `target_id`), INDEX `idx_target` (`target_type`, `target_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
关键设计:UNIQUE联合索引防止重复点赞,target_type支持多态关联,便于扩展问答、视频等新模块。
收藏表(bookmarks)
CREATE TABLE `bookmarks` ( `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `user_id` INT UNSIGNED NOT NULL, `target_type` VARCHAR(50) NOT NULL, `target_id` INT UNSIGNED NOT NULL, `folder_id` INT UNSIGNED DEFAULT 0 COMMENT '收藏夹ID', `remarks` VARCHAR(255) DEFAULT '' COMMENT '备注标签', `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY `uk_user_target` (`user_id`, `target_type`, `target_id`), INDEX `idx_folder` (`folder_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
优化要点:新增folder_id字段支持多重收藏夹分类,remarks便于用户添加个性化描述。
计数服务表(推荐)
CREATE TABLE `counters` ( `target_type` VARCHAR(50) NOT NULL, `target_id` INT UNSIGNED NOT NULL, `likes_count` INT UNSIGNED DEFAULT 0, `bookmarks_count` INT UNSIGNED DEFAULT 0, `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`target_type`, `target_id`) ) ENGINE=InnoDB;
优势:将计数从主表中剥离,避免UPDATE操作阻塞内容读取。
点赞功能的PHP实现(含防刷机制)
基础核心代码
<?php
class LikeService
{
public function toggle($userId, $type, $targetId)
{
// 1. 防重复验证(利用唯一索引)
$like = LikeModel::where(['user_id' => $userId, 'target_type' => $type, 'target_id' => $targetId])->first();
// 2. 事务处理
DB::transaction(function () use ($userId, $type, $targetId, $like) {
if ($like) {
$like->delete();
Counter::decrement($type, $targetId, 'likes_count');
} else {
LikeModel::create(['user_id' => $userId, 'target_type' => $type, 'target_id' => $targetId]);
Counter::increment($type, $targetId, 'likes_count');
}
});
// 3. 返回最新计数
return Counter::get($type, $targetId, 'likes_count');
}
}
防刷机制
- IP频率限制(Redis + 滑动窗口):
$key = "like_rate_limit:{$userId}:{$type}:{$targetId}"; $limit = 10; // 5秒内最多操作10次 $window = 5; $currentCount = Redis::incr($key); if ($currentCount === 1) Redis::expire($key, $window); if ($currentCount > $limit) { throw new \Exception('操作过于频繁'); } - 反爬虫策略:对点赞行为添加
signature签名(用户ID + 时间戳 + 密钥加密),服务端验证签名时效性。
收藏功能的多态关联设计
使用PHP的服务模式封装多态逻辑,避免switch语句:
class BookmarkService
{
protected $models = [
'article' => \App\Models\Article::class,
'post' => \App\Models\Post::class,
];
public function collect($userId, $type, $targetId, $folderId = 0)
{
// 通过多态关联获取主体模型
$modelClass = $this->models[$type];
$target = $modelClass::findOrFail($targetId);
// 执行收藏
$bookmark = $target->bookmarks()->create([
'user_id' => $userId,
'folder_id' => $folderId
]);
return $bookmark;
}
}
前端交互优化:采用“即时生效 + 后端异步确认”模式,AJAX请求发起后,先更新前端UI(图标变亮、计数+1),后台再发送持久化请求,若失败则回滚UI并提示错误。
性能优化与缓存策略
Redis缓存方案
- 热点数据:使用
Hash结构缓存内容热度:likes_count:article:123 => 1540 bookmarks_count:article:123 => 320 - 用户状态:
Set结构存储用户已点赞/收藏的ID列表:user:456:liked:articles => {123, 456, 789} user:456:bookmarked:articles => {124, 567} - 写入策略:先更新Redis,再异步批量写入MySQL(每秒同步一次或每100条同步一次)。
数据库优化
- 分区表:按
target_type对likes表进行范围分区,加速单月数据查询。 - 软删除替代物理删除:在
likes表中增加deleted_at字段,便于数据恢复与审计。
常见问题解答(Q&A)
Q1:如何解决用户点赞后计数不同步的问题?
A:采用最终一致性方案,前端展示的计数来自Redis,MySQL负责持久化,使用消息队列(如RabbitMQ)异步同步计数,若Redis宕机,可从MySQL重新加载计数到缓存。
Q2:收藏功能是否应该限制用户最大收藏数?
A:建议限制,防止恶意刷数据,可在MySQL中增加触发器或应用层校验,普通用户最多收藏1000条,VIP用户无限”。
Q3:点赞/收藏数据是否需要备份?
A:由于数据量可能极大(亿级),建议仅备份核心数据结构(表结构 + 增量操作日志),恢复时通过日志回放重建。
Q4:如何支持取消点赞/收藏时的原子性操作?
A:使用Lua脚本在Redis中保证删除记录 + 减少计数的原子性,或在MySQL中使用事务包裹DELETE与UPDATE。
Q5:多人同时点赞/收藏时如何避免超卖?
A:计数更新使用Redis的INCR原子指令,无需加锁,如果需要展示“当前用户是否已操作”,通过Redis的SISMEMBER判断,而非数据库查询。
总结与扩展建议
点赞与收藏功能虽小,却涉及并发控制、防刷机制、缓存策略、多态设计等核心架构知识,建议开发者:
- 使用单元测试确保
toggle方法的正确性,特别是边界条件(同一用户连续点击100次)。 - 在管理后台提供热力统计(按小时、按地区分析点赞峰值)。
- 考虑WebSocket实时推送:当用户点赞/收藏时,通过WebSocket通知其他在线用户更新UI,提升互动感。
务必关注用户体验:为“取消点赞”提供明确的视觉反馈(如渐变动画),并为收藏功能提供“查看收藏夹”的快捷入口,降低用户操作成本。
参考资源:Laravel官方文档(多态关联章节)、Redis INCR命令详解、《高性能MySQL》第14章(计数优化)。