PHP项目如何实现关注功能?从入门到实战的完整指南
目录导读
- 关注功能的核心逻辑与数据库设计
- 基于PHP+MySQL的实现步骤
- 前后端交互与状态管理
- 性能优化与防重复操作
- 常见问题与解决方案(问答环节)
关注功能的核心逻辑与数据库设计
在社交媒体、内容平台或电商系统中,“关注”是用户间建立单向社交关系的基础操作,其本质是记录用户A关注用户B这一行为,并支持后续的“取消关注”“查询是否关注”“获取粉丝/关注列表”等扩展功能。

数据库表设计
推荐使用一张独立的follows表来存储关系,避免与用户主表耦合:
CREATE TABLE `follows` ( `id` int(11) NOT NULL AUTO_INCREMENT, `follower_id` int(11) NOT NULL COMMENT '关注者ID', `following_id` int(11) NOT NULL COMMENT '被关注者ID', `created_at` timestamp DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `unique_follow` (`follower_id`,`following_id`), -- 防止重复关注 INDEX `idx_follower` (`follower_id`), INDEX `idx_following` (`following_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
设计要点:
UNIQUE索引确保同一对关注关系只存在一条记录(即“关注”为幂等操作)。- 索引按
follower_id和following_id分别建立,便于快速查询用户的关注/粉丝列表。 - 不要将关注数直接存为
user表的字段,避免频繁更新带来的锁竞争,应通过COUNT查询实时统计或使用Redis缓存。
基于PHP+MySQL的实现步骤
1 关注/取消关注接口
以下是一个典型的RESTful API实现,使用PDO预处理防止SQL注入:
<?php
class FollowController {
public function toggleFollow($currentUserId, $targetUserId) {
// 1. 参数校验
if ($currentUserId == $targetUserId) {
throw new Exception("不能关注自己");
}
// 2. 检查是否已关注
$stmt = $pdo->prepare("SELECT id FROM follows WHERE follower_id = ? AND following_id = ?");
$stmt->execute([$currentUserId, $targetUserId]);
$existing = $stmt->fetch();
if ($existing) {
// 已关注 → 取消关注
$deleteStmt = $pdo->prepare("DELETE FROM follows WHERE id = ?");
$deleteStmt->execute([$existing['id']]);
return ['action' => 'unfollowed', 'status' => false];
} else {
// 未关注 → 添加关注
$insertStmt = $pdo->prepare("INSERT INTO follows (follower_id, following_id) VALUES (?, ?)");
$insertStmt->execute([$currentUserId, $targetUserId]);
return ['action' => 'followed', 'status' => true];
}
}
}
2 查询是否关注
在展示用户主页时,需要快速判断当前登录用户是否已关注对方:
public function isFollowing($currentUserId, $targetUserId) {
$stmt = $pdo->prepare("SELECT 1 FROM follows WHERE follower_id = ? AND following_id = ? LIMIT 1");
$stmt->execute([$currentUserId, $targetUserId]);
return (bool)$stmt->fetchColumn();
}
3 获取关注/粉丝列表
使用JOIN查询,并做分页处理:
public function getFollowingList($userId, $page = 1, $perPage = 20) {
$offset = ($page - 1) * $perPage;
$stmt = $pdo->prepare("
SELECT u.id, u.username, u.avatar
FROM follows f
JOIN users u ON f.following_id = u.id
WHERE f.follower_id = ?
ORDER BY f.created_at DESC
LIMIT ? OFFSET ?
");
$stmt->execute([$userId, $perPage, $offset]);
return $stmt->fetchAll();
}
性能提示:当粉丝/关注数量超过10万时,避免使用COUNT全表扫描,可维护一个Redis计数器,或使用MySQL的EXPLAIN优化索引。
前后端交互与状态管理
1 前端按钮状态切换
以下为Vue.js的示例逻辑,核心在于乐观更新(先修改UI状态,再发送请求):
<template>
<button @click="toggleFollow" :class="{ 'is-following': isFollowing }">
{{ isFollowing ? '已关注' : '关注' }}
</button>
</template>
<script>
export default {
data() { return { isFollowing: false } },
methods: {
async toggleFollow() {
// 乐观更新:立即翻转状态
this.isFollowing = !this.isFollowing;
try {
const res = await axios.post('/api/follow', { targetUserId: this.userId });
// 服务器返回的最终状态覆盖本地
this.isFollowing = res.data.status;
} catch (error) {
// 请求失败时回滚状态
this.isFollowing = !this.isFollowing;
console.error('关注操作失败', error);
}
}
}
}
</script>
2 防止重复点击
在请求返回前,禁用按钮:
this.isProcessing = true; this.isFollowing = !this.isFollowing; // ... 请求完成后 this.isProcessing = false;
性能优化与防重复操作
1 数据库唯一索引防重复
如前文设计,UNIQUE (follower_id, following_id)从数据库层面杜绝了同一用户对同一目标重复关注的可能,即使前端没有做防抖处理。
2 Redis缓存计数器
对于“粉丝数”“关注数”这种高频读取字段,使用Redis缓存避免每次查询都扫描表:
// 关注后更新
$redis->incr("user:{$targetUserId}:follower_count");
$redis->incr("user:{$currentUserId}:following_count");
// 获取时优先读缓存
$count = $redis->get("user:{$userId}:follower_count");
if ($count === false) {
$count = $pdo->query("SELECT COUNT(*) FROM follows WHERE following_id = $userId")->fetchColumn();
$redis->setex("user:{$userId}:follower_count", 3600, $count);
}
3 异步队列处理
在百万级用户平台,直接将关注操作写入数据库可能影响主库性能,可采用消息队列(RabbitMQ/Redis List)异步处理:
// 生产者(API层)
$redis->lpush('follow_queue', json_encode(['follower' => $uid, 'following' => $tid]));
// 消费者(Worker进程)
while ($data = $redis->brpop('follow_queue', 5)) {
$info = json_decode($data[1], true);
$db->insert('follows', $info);
// 更新缓存...
}
常见问题与解决方案(问答环节)
Q1:用户关注自己怎么办?
A:在业务层做校验,如 if ($currentUserId == $targetUserId) 直接返回错误提示,数据库层面无法通过索引约束阻止,必须业务处理。
Q2:如何确保关注按钮的实时状态?
A:推荐后端在用户访问主页时,一次性返回该用户关注的所有ID列表(如 [3,5,8]),前端用Set存储,切换时使用乐观更新。
Q3:关注后需要发送通知吗?
A:典型做法是异步推送到通知表,例如关注后创建一个 notifications 记录(type=follow, from_id=关注者, to_id=被关注者),通过WebSocket或定时任务推送。
Q4:如何实现“互关”(双向关注)的标识?
A:查询时做两次检查:
SELECT EXISTS(SELECT 1 FROM follows WHERE follower_id=? AND following_id=?) AS i_follow_him, EXISTS(SELECT 1 FROM follows WHERE follower_id=? AND following_id=?) AS he_follows_me
当两个值都为1时,在前端显示“互相关注”状态。
Q5:关注列表分页如何优化?
A:使用基于游标的分页(cursor-based pagination),而非OFFSET。WHERE created_at < 上次最后一条的时间 LIMIT 20,避免OFFSET在大数据量下的性能下降。
实现关注功能的核心是通过关系表存储用户间的关联,辅以唯一索引防止重复、乐观更新提升交互体验、缓存和异步处理增强性能,无论是搭建个人项目还是百万级社交平台,上述方案均可作为基础架构延伸扩展,实际开发中,请根据用户量级和服务器资源选择合适的优化策略,并始终关注数据一致性与并发控制。