本文目录导读:

这是一个非常典型的社交功能需求,实现用户关注功能,核心是在数据库中记录 “谁关注了谁” 的关系。
我将从数据库设计、核心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_id和following_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结果
↓
前端更新按钮状态和数字
核心要点:
- 数据库:使用
follows表 + 唯一索引 + 双字段索引。 - SQL:
INSERT和DELETE切换,配合唯一索引防止重复。 - 性能:冗余计数字段 + 缓存(可选)。
- 交互:Ajax 实现无刷新关注。
如果你使用的是 Laravel 框架,可以考虑使用 Eloquent 的 attach/detach 方法,或者使用 laravel-follow 这类扩展包,可以省去很多底层代码,如果需要原生 PHP 的完整示例,上面的代码已经足够落地使用。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播