PHP项目点赞与收藏功能

wen PHP项目 2

PHP项目点赞与收藏功能:从零搭建高效交互模块

目录导读

  1. 功能设计核心思路
  2. 数据库表结构优化方案
  3. 点赞功能的PHP实现(含防刷机制)
  4. 收藏功能的多态关联设计
  5. 性能优化与缓存策略
  6. 常见问题解答(Q&A)
  7. 总结与扩展建议

功能设计核心思路

在PHP项目中实现点赞与收藏功能,首先需要明确业务逻辑:点赞通常要求一人一次有效点击(可取消),收藏则需区分用户私有集合公开收藏,设计时应遵循高内聚低耦合原则,将交互逻辑与业务主体解耦。

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_typelikes表进行范围分区,加速单月数据查询。
  • 软删除替代物理删除:在likes表中增加deleted_at字段,便于数据恢复与审计。

常见问题解答(Q&A)

Q1:如何解决用户点赞后计数不同步的问题?
A:采用最终一致性方案,前端展示的计数来自Redis,MySQL负责持久化,使用消息队列(如RabbitMQ)异步同步计数,若Redis宕机,可从MySQL重新加载计数到缓存。

Q2:收藏功能是否应该限制用户最大收藏数?
A:建议限制,防止恶意刷数据,可在MySQL中增加触发器或应用层校验,普通用户最多收藏1000条,VIP用户无限”。

Q3:点赞/收藏数据是否需要备份?
A:由于数据量可能极大(亿级),建议仅备份核心数据结构(表结构 + 增量操作日志),恢复时通过日志回放重建。

Q4:如何支持取消点赞/收藏时的原子性操作?
A:使用Lua脚本在Redis中保证删除记录 + 减少计数的原子性,或在MySQL中使用事务包裹DELETEUPDATE

Q5:多人同时点赞/收藏时如何避免超卖?
A:计数更新使用Redis的INCR原子指令,无需加锁,如果需要展示“当前用户是否已操作”,通过Redis的SISMEMBER判断,而非数据库查询。


总结与扩展建议

点赞与收藏功能虽小,却涉及并发控制、防刷机制、缓存策略、多态设计等核心架构知识,建议开发者:

  • 使用单元测试确保toggle方法的正确性,特别是边界条件(同一用户连续点击100次)。
  • 在管理后台提供热力统计(按小时、按地区分析点赞峰值)。
  • 考虑WebSocket实时推送:当用户点赞/收藏时,通过WebSocket通知其他在线用户更新UI,提升互动感。

务必关注用户体验:为“取消点赞”提供明确的视觉反馈(如渐变动画),并为收藏功能提供“查看收藏夹”的快捷入口,降低用户操作成本。


参考资源:Laravel官方文档(多态关联章节)、Redis INCR命令详解、《高性能MySQL》第14章(计数优化)。

抱歉,评论功能暂时关闭!