本文目录导读:

我来为你详细讲解PHP社区动态功能中的关注/粉丝系统实现方案。
数据库设计
关注关系表
CREATE TABLE `user_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` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`status` tinyint(1) DEFAULT 1 COMMENT '1:关注 0:取消关注',
PRIMARY KEY (`id`),
UNIQUE KEY `unique_follow` (`follower_id`, `following_id`),
KEY `idx_follower` (`follower_id`),
KEY `idx_following` (`following_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
用户表增加统计字段
ALTER TABLE `users` ADD COLUMN `followers_count` int(11) DEFAULT 0 COMMENT '粉丝数', ADD COLUMN `following_count` int(11) DEFAULT 0 COMMENT '关注数';
核心功能实现
关注/取关操作类
<?php
class FollowService {
private $db;
public function __construct($db) {
$this->db = $db;
}
/**
* 关注用户
* @param int $followerId 当前用户ID
* @param int $followingId 被关注用户ID
* @return array
*/
public function follow($followerId, $followingId) {
// 不能关注自己
if ($followerId == $followingId) {
return ['code' => 0, 'msg' => '不能关注自己'];
}
try {
$this->db->beginTransaction();
// 检查是否已关注
$follow = $this->checkFollow($followerId, $followingId);
if ($follow) {
if ($follow['status'] == 1) {
return ['code' => 0, 'msg' => '已关注该用户'];
}
// 重新关注(恢复)
$sql = "UPDATE user_follows SET status = 1, created_at = NOW()
WHERE follower_id = ? AND following_id = ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$followerId, $followingId]);
} else {
// 首次关注
$sql = "INSERT INTO user_follows (follower_id, following_id) VALUES (?, ?)";
$stmt = $this->db->prepare($sql);
$stmt->execute([$followerId, $followingId]);
}
// 更新计数
$this->updateFollowCount($followerId, $followingId, 'increment');
$this->db->commit();
// 发送通知
$this->sendFollowNotification($followerId, $followingId);
return ['code' => 1, 'msg' => '关注成功'];
} catch (Exception $e) {
$this->db->rollBack();
return ['code' => 0, 'msg' => '操作失败: ' . $e->getMessage()];
}
}
/**
* 取消关注
*/
public function unfollow($followerId, $followingId) {
try {
$this->db->beginTransaction();
$sql = "UPDATE user_follows SET status = 0 WHERE follower_id = ? AND following_id = ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$followerId, $followingId]);
// 更新计数
$this->updateFollowCount($followerId, $followingId, 'decrement');
$this->db->commit();
return ['code' => 1, 'msg' => '已取消关注'];
} catch (Exception $e) {
$this->db->rollBack();
return ['code' => 0, 'msg' => '操作失败'];
}
}
/**
* 检查关注关系
*/
public function checkFollow($followerId, $followingId) {
$sql = "SELECT * FROM user_follows WHERE follower_id = ? AND following_id = ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$followerId, $followingId]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
/**
* 获取关注状态
*/
public function getFollowStatus($followerId, $followingId) {
$follow = $this->checkFollow($followerId, $followingId);
return $follow && $follow['status'] == 1;
}
/**
* 更新关注计数
*/
private function updateFollowCount($followerId, $followingId, $action) {
$operator = ($action == 'increment') ? '+' : '-';
// 更新关注者的关注数
$sql = "UPDATE users SET following_count = following_count {$operator} 1 WHERE id = ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$followerId]);
// 更新被关注者的粉丝数
$sql = "UPDATE users SET followers_count = followers_count {$operator} 1 WHERE id = ?";
$stmt = $this->db->prepare($sql);
$stmt->execute([$followingId]);
}
/**
* 发送关注通知
*/
private function sendFollowNotification($followerId, $followingId) {
// 获取关注者信息
$follower = $this->getUserInfo($followerId);
// 创建通知
$notificationData = [
'user_id' => $followingId,
'type' => 'follow',
'content' => "用户 {$follower['username']} 关注了你",
'from_user_id' => $followerId,
'created_at' => date('Y-m-d H:i:s')
];
// 插入通知表
$this->createNotification($notificationData);
}
}
动态流实现
获取关注者动态
<?php
class FeedService {
private $db;
/**
* 获取关注用户的动态(时间线)
*/
public function getFollowFeed($userId, $page = 1, $limit = 20) {
$offset = ($page - 1) * $limit;
// 获取关注的活跃用户ID列表
$followingIds = $this->getFollowingIds($userId);
if (empty($followingIds)) {
return ['list' => [], 'total' => 0];
}
$placeholders = implode(',', array_fill(0, count($followingIds), '?'));
// 查询动态(帖子、评论、点赞等)
$sql = "SELECT
p.*,
u.username,
u.avatar,
u.id as user_id
FROM posts p
INNER JOIN users u ON p.user_id = u.id
WHERE p.user_id IN ({$placeholders})
AND p.status = 1
ORDER BY p.created_at DESC
LIMIT ? OFFSET ?";
$stmt = $this->db->prepare($sql);
$params = array_merge($followingIds, [$limit, $offset]);
$stmt->execute($params);
$posts = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 统计总数
$countSql = "SELECT COUNT(*) FROM posts WHERE user_id IN ({$placeholders}) AND status = 1";
$countStmt = $this->db->prepare($countSql);
$countStmt->execute($followingIds);
$total = $countStmt->fetchColumn();
return [
'list' => $posts,
'total' => $total,
'page' => $page,
'limit' => $limit
];
}
/**
* 获取关注的用户列表
*/
private function getFollowingIds($userId) {
$sql = "SELECT following_id FROM user_follows
WHERE follower_id = ? AND status = 1
ORDER BY created_at DESC";
$stmt = $this->db->prepare($sql);
$stmt->execute([$userId]);
return $stmt->fetchAll(PDO::FETCH_COLUMN);
}
}
前端交互实现
HTML模板
<!-- 关注按钮 -->
<div class="follow-btn-container">
<button class="btn btn-follow"
data-user-id="<?= $targetUserId ?>"
data-is-following="<?= $isFollowing ?>"
onclick="toggleFollow(this)">
<?php if ($isFollowing): ?>
已关注
<?php else: ?>
+ 关注
<?php endif; ?>
</button>
</div>
<!-- 动态流 -->
<div id="feed-container">
<!-- 动态列表 -->
</div>
JavaScript实现
class FollowManager {
constructor() {
this.ajaxUrl = '/api/follow.php';
}
/**
* 切换关注状态
*/
toggleFollow(btn) {
const userId = btn.dataset.userId;
const isFollowing = btn.dataset.isFollowing === 'true';
// 禁用按钮防止重复点击
btn.disabled = true;
const action = isFollowing ? 'unfollow' : 'follow';
fetch(this.ajaxUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': this.getCsrfToken()
},
body: JSON.stringify({
action: action,
target_user_id: userId
})
})
.then(response => response.json())
.then(data => {
if (data.code === 1) {
// 更新UI
if (action === 'follow') {
btn.dataset.isFollowing = 'true';
btn.textContent = '已关注';
btn.classList.add('following');
} else {
btn.dataset.isFollowing = 'false';
btn.textContent = '+ 关注';
btn.classList.remove('following');
}
// 更新粉丝数
this.updateFollowerCount(userId, data.followers_count);
// 刷新动态流
if (data.feed_update) {
this.refreshFeed();
}
} else {
alert(data.msg);
}
})
.catch(error => {
console.error('Error:', error);
alert('操作失败,请重试');
})
.finally(() => {
btn.disabled = false;
});
}
/**
* 加载更多动态
*/
loadMoreFeed(page) {
const feedContainer = document.getElementById('feed-container');
fetch(`/api/feed.php?page=${page}&limit=20`, {
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
if (data.list.length > 0) {
// 追加动态到页面
data.list.forEach(item => {
feedContainer.appendChild(this.createFeedItem(item));
});
} else {
// 没有更多动态
document.querySelector('.load-more').textContent = '没有更多了';
}
});
}
/**
* 创建动态元素
*/
createFeedItem(item) {
const div = document.createElement('div');
div.className = 'feed-item';
div.innerHTML = `
<div class="feed-header">
<img src="${item.avatar}" alt="avatar" class="avatar">
<span class="username">${item.username}</span>
<span class="time">${this.formatTime(item.created_at)}</span>
</div>
<div class="feed-content">
${item.content}
</div>
<div class="feed-actions">
<button onclick="likePost(${item.id})">赞 (${item.likes})</button>
<button onclick="commentPost(${item.id})">评论 (${item.comments})</button>
</div>
`;
return div;
}
/**
* 时间格式化
*/
formatTime(time) {
const diff = Date.now() - new Date(time).getTime();
const minutes = Math.floor(diff / 60000);
if (minutes < 1) return '刚刚';
if (minutes < 60) return `${minutes}分钟前`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `${hours}小时前`;
const days = Math.floor(hours / 24);
if (days < 7) return `${days}天前`;
return time;
}
}
// 初始化
const followManager = new FollowManager();
API接口示例
follow.php
<?php
header('Content-Type: application/json');
// 验证登录
session_start();
if (!isset($_SESSION['user_id'])) {
die(json_encode(['code' => 0, 'msg' => '请先登录']));
}
$input = json_decode(file_get_contents('php://input'), true);
$action = $input['action'] ?? '';
$targetUserId = $input['target_user_id'] ?? 0;
$followService = new FollowService($db);
$currentUserId = $_SESSION['user_id'];
switch ($action) {
case 'follow':
$result = $followService->follow($currentUserId, $targetUserId);
break;
case 'unfollow':
$result = $followService->unfollow($currentUserId, $targetUserId);
break;
default:
$result = ['code' => 0, 'msg' => '无效操作'];
}
echo json_encode($result);
优化建议
性能优化
// 使用Redis缓存关注关系
class FollowCache {
private $redis;
public function isFollowing($followerId, $followingId) {
$key = "user:{$followerId}:following";
return $this->redis->sIsMember($key, $followingId);
}
public function getFollowingIds($userId) {
$key = "user:{$userId}:following";
return $this->redis->sMembers($key);
}
}
注意事项
- 幂等性处理:确保重复点击不会产生重复关注记录
- 事务处理:关注操作涉及多表更新,使用事务保证一致性
- 缓存策略:热门用户的粉丝数可缓存,减少数据库压力
- 异步处理:通知发送可使用消息队列异步处理
- 防刷机制:限制单个用户每天关注数量
这个实现方案涵盖了关注功能的核心逻辑,你可以根据具体需求进行调整和扩展。