PHP项目怎么实现用户关注功能?

wen PHP项目 34

本文目录导读:

PHP项目怎么实现用户关注功能?

  1. 数据库设计
  2. 核心PHP代码实现
  3. 前端交互(AJAX 示例)
  4. 性能优化与注意事项
  5. 完整功能流程图

这是一个非常典型的社交功能需求,实现用户关注功能,核心是在数据库中记录 “谁关注了谁” 的关系。

我将从数据库设计核心SQL操作性能优化代码示例四个方面来详细说明如何在PHP项目中实现。

数据库设计

最常见的做法是创建一个单独的关系表(多对多关系表),记录每一条关注关系。

CREATE TABLE `follows` (
    `id` BIGINT UNSIGNED AUTO_INCREMENT,
    `follower_id` BIGINT UNSIGNED NOT NULL COMMENT '关注者用户ID',
    `following_id` BIGINT UNSIGNED NOT NULL COMMENT '被关注者用户ID',
    `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '关注时间',
    PRIMARY KEY (`id`),
    UNIQUE KEY `unique_follow` (`follower_id`, `following_id`), -- 防止重复关注
    INDEX `idx_follower_id` (`follower_id`), -- 用于查询“我关注的人”
    INDEX `idx_following_id` (`following_id`) -- 用于查询“我的粉丝”
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户关注关系表';

关键点说明:

  • 唯一索引UNIQUE KEY unique_follow确保了同一个人不会重复关注同一个人。
  • 索引:为follower_idfollowing_id分别建立索引,这是查询性能的关键。

核心PHP代码实现

以下代码使用 PDO(推荐方式)并假设你有一个$user_id表示当前登录用户。

1 关注与取消关注(切换逻辑)

<?php
/**
 * 切换关注状态(关注/取消关注)
 * @param PDO $pdo
 * @param int $followerId  当前用户ID
 * @param int $followingId 目标用户ID
 * @return bool|string     成功返回true,失败返回错误信息
 */
function toggleFollow(PDO $pdo, int $followerId, int $followingId): mixed {
    // 1. 基础校验:不能关注自己
    if ($followerId === $followingId) {
        return '不能关注自己';
    }
    // 2. 检查关系是否存在
    $checkSql = "SELECT id FROM follows 
                 WHERE follower_id = :follower_id AND following_id = :following_id";
    $stmt = $pdo->prepare($checkSql);
    $stmt->execute([
        ':follower_id' => $followerId,
        ':following_id' => $followingId
    ]);
    $follow = $stmt->fetch();
    if ($follow) {
        // 3. 已关注 -> 取消关注(DELETE)
        $deleteSql = "DELETE FROM follows WHERE id = :id";
        $stmt = $pdo->prepare($deleteSql);
        $stmt->execute([':id' => $follow['id']]);
        return false; // 或者返回 '已取消关注'
    } else {
        // 4. 未关注 -> 添加关注(INSERT)
        // 注意:由于有唯一索引,直接用INSERT,如果重复会抛异常,但我们已提前检查
        $insertSql = "INSERT INTO follows (follower_id, following_id) 
                      VALUES (:follower_id, :following_id)";
        $stmt = $pdo->prepare($insertSql);
        $stmt->execute([
            ':follower_id' => $followerId,
            ':following_id' => $followingId
        ]);
        return true; // 或者返回 '关注成功'
    }
}

2 检查是否已关注

<?php
function isFollowing(PDO $pdo, int $currentUserId, int $targetUserId): bool {
    if ($currentUserId === $targetUserId) return false; // 自己不关注自己
    $sql = "SELECT 1 FROM follows 
            WHERE follower_id = :follower_id AND following_id = :following_id
            LIMIT 1";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([
        ':follower_id' => $currentUserId,
        ':following_id' => $targetUserId
    ]);
    return (bool) $stmt->fetchColumn();
}

3 获取关注列表与粉丝列表

<?php
// 获取我关注的人 (Following)
function getFollowingUsers(PDO $pdo, int $userId): array {
    $sql = "SELECT u.id, u.username, u.avatar, f.created_at as followed_at
            FROM follows f
            JOIN users u ON f.following_id = u.id
            WHERE f.follower_id = :user_id
            ORDER BY f.created_at DESC";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([':user_id' => $userId]);
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// 获取我的粉丝 (Followers)
function getFollowerUsers(PDO $pdo, int $userId): array {
    $sql = "SELECT u.id, u.username, u.avatar, f.created_at as followed_at
            FROM follows f
            JOIN users u ON f.follower_id = u.id
            WHERE f.following_id = :user_id
            ORDER BY f.created_at DESC";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([':user_id' => $userId]);
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

前端交互(AJAX 示例)

为了提高用户体验,通常使用 Ajax 实现无刷新关注。

<!-- 关注按钮 -->
<button class="follow-btn" data-target-id="<?= $targetUserId ?>">
    <?= isFollowing($pdo, $currentUserId, $targetUserId) ? '已关注' : '+ 关注' ?>
</button>
<script>
document.querySelector('.follow-btn').addEventListener('click', function() {
    const btn = this;
    const targetId = btn.dataset.targetId;
    fetch('/api/follow', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ target_user_id: targetId })
    })
    .then(res => res.json())
    .then(data => {
        if (data.status === 'followed') {
            btn.textContent = '已关注';
            btn.classList.add('following');
        } else if (data.status === 'unfollowed') {
            btn.textContent = '+ 关注';
            btn.classList.remove('following');
        } else {
            alert(data.message);
        }
    });
});
</script>

对应的PHP后端接口 /api/follow.php

<?php
// 假设已启动session并验证用户登录
$sessionUserId = $_SESSION['user_id'];
$input = json_decode(file_get_contents('php://input'), true);
$targetUserId = (int)($input['target_user_id'] ?? 0);
if ($targetUserId <= 0) {
    echo json_encode(['status' => 'error', 'message' => '无效的用户']);
    exit;
}
try {
    $result = toggleFollow($pdo, $sessionUserId, $targetUserId);
    if ($result === true) {
        echo json_encode(['status' => 'followed']);
    } elseif ($result === false) {
        echo json_encode(['status' => 'unfollowed']);
    } else {
        echo json_encode(['status' => 'error', 'message' => $result]);
    }
} catch (Exception $e) {
    http_response_code(500);
    echo json_encode(['status' => 'error', 'message' => '服务器错误']);
}

性能优化与注意事项

1 缓存关注数

每次显示用户主页时都去COUNT粉丝数会影响性能,建议在users表中增加两个冗余字段:

ALTER TABLE users 
ADD COLUMN followers_count INT NOT NULL DEFAULT 0 COMMENT '粉丝数',
ADD COLUMN following_count INT NOT NULL DEFAULT 0 COMMENT '关注数';

在关注/取消关注时,同时更新这两个字段:

// 在 toggleFollow 函数中,执行 INSERT/DELETE 后,同步更新计数
if ($follow) {
    // 取消关注
    $pdo->exec("UPDATE users SET followers_count = followers_count - 1 WHERE id = $followingId");
    $pdo->exec("UPDATE users SET following_count = following_count - 1 WHERE id = $followerId");
} else {
    // 加关注
    $pdo->exec("UPDATE users SET followers_count = followers_count + 1 WHERE id = $followingId");
    $pdo->exec("UPDATE users SET following_count = following_count + 1 WHERE id = $followerId");
}

2 使用 Redis 或 Memcached 缓存

对于高并发场景,可以将“某人是否关注了另一个人”的查询结果缓存起来:

// 伪代码 - 使用 Redis
$cacheKey = "follow:{$followerId}_{$followingId}";
$isFollowed = $redis->get($cacheKey);
if ($isFollowed === null) {
    // 查数据库
    $isFollowed = isFollowing($pdo, $followerId, $followingId);
    $redis->setex($cacheKey, 3600, $isFollowed); // 缓存1小时
}

3 防止循环关注

你可以在业务层增加判断:如果B关注了A,A就不能再关注B(类似Twitter的设计),或者允许双向关注(类似Instagram),根据你的产品逻辑选择。


完整功能流程图

用户点击关注按钮
      ↓
Ajax 请求到 /api/follow
      ↓
PHP接收请求,验证用户登录
      ↓
检查数据库中是否已存在关注关系
      ↓
    ┌───────┴───────┐
  存在(取消关注)   不存在(添加关注)
    ↓                ↓
  删除记录          插入记录
    ↓                ↓
  更新粉丝/关注计数  更新粉丝/关注计数
    ↓                ↓
  返回JSON结果      返回JSON结果
      ↓
前端更新按钮状态和数字

核心要点:

  1. 数据库:使用follows表 + 唯一索引 + 双字段索引。
  2. SQLINSERTDELETE 切换,配合唯一索引防止重复。
  3. 性能:冗余计数字段 + 缓存(可选)。
  4. 交互:Ajax 实现无刷新关注。

如果你使用的是 Laravel 框架,可以考虑使用 Eloquentattach/detach 方法,或者使用 laravel-follow 这类扩展包,可以省去很多底层代码,如果需要原生 PHP 的完整示例,上面的代码已经足够落地使用。

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