PHP关注系统怎么设计数据库

wen PHP项目 3

本文目录导读:

PHP关注系统怎么设计数据库

  1. 基础数据库表设计
  2. 增强版数据库设计
  3. PHP操作类示例
  4. 性能优化建议
  5. 注意事项

我来为你设计一个完整的PHP关注系统数据库方案。

基础数据库表设计

核心关注表(最简方案)

CREATE TABLE `follows` (
    `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `follower_id` BIGINT UNSIGNED NOT NULL COMMENT '关注者ID',
    `following_id` BIGINT UNSIGNED NOT NULL COMMENT '被关注者ID',
    `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY `uk_follower_following` (`follower_id`, `following_id`),
    KEY `idx_following_id` (`following_id`),
    KEY `idx_created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

增强版数据库设计

用户表(基础信息)

CREATE TABLE `users` (
    `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `username` VARCHAR(50) NOT NULL UNIQUE,
    `nickname` VARCHAR(50) NOT NULL,
    `avatar` VARCHAR(255),
    `followers_count` INT UNSIGNED DEFAULT 0 COMMENT '粉丝数(冗余,便于查询)',
    `following_count` INT UNSIGNED DEFAULT 0 COMMENT '关注数(冗余)',
    `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

关注关系表(增强版)

CREATE TABLE `follows` (
    `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `follower_id` BIGINT UNSIGNED NOT NULL COMMENT '关注者ID',
    `following_id` BIGINT UNSIGNED NOT NULL COMMENT '被关注者ID',
    `status` TINYINT DEFAULT 1 COMMENT '状态:1-关注,0-取消',
    `source` VARCHAR(50) DEFAULT '' COMMENT '关注来源:recommend/search/share等',
    `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    -- 联合唯一索引确保唯一关注关系
    UNIQUE KEY `uk_follower_following` (`follower_id`, `following_id`),
    -- 查询优化索引
    KEY `idx_following_status` (`following_id`, `status`),
    KEY `idx_created_at` (`created_at`),
    -- 外键约束(可选,视性能需求)
    -- FOREIGN KEY (`follower_id`) REFERENCES `users`(`id`),
    -- FOREIGN KEY (`following_id`) REFERENCES `users`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

互相关注表(可选,提高查询效率)

CREATE TABLE `mutual_follows` (
    `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `user1_id` BIGINT UNSIGNED NOT NULL,
    `user2_id` BIGINT UNSIGNED NOT NULL,
    `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY `uk_user1_user2` (`user1_id`, `user2_id`),
    UNIQUE KEY `uk_user2_user1` (`user2_id`, `user1_id`),
    KEY `idx_user1_id` (`user1_id`),
    KEY `idx_user2_id` (`user2_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

关注通知表(可选)

CREATE TABLE `follow_notifications` (
    `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `user_id` BIGINT UNSIGNED NOT NULL COMMENT '接收通知的用户ID',
    `follower_id` BIGINT UNSIGNED NOT NULL COMMENT '关注者ID',
    `is_read` TINYINT DEFAULT 0 COMMENT '是否已读',
    `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    KEY `idx_user_is_read` (`user_id`, `is_read`),
    KEY `idx_created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

PHP操作类示例

Follow模型类

<?php
class FollowModel {
    private $db;
    public function __construct($db) {
        $this->db = $db;
    }
    /**
     * 关注用户
     */
    public function follow($followerId, $followingId, $source = '') {
        if ($followerId == $followingId) {
            return ['error' => '不能关注自己'];
        }
        try {
            // 开启事务
            $this->db->beginTransaction();
            // 插入关注记录
            $sql = "INSERT INTO follows (follower_id, following_id, source) 
                    VALUES (?, ?, ?)";
            $stmt = $this->db->prepare($sql);
            $stmt->execute([$followerId, $followingId, $source]);
            // 更新计数(使用原子操作避免并发问题)
            $this->updateCounts($followerId, $followingId, 'follow');
            // 检查是否互相关注
            $isMutual = $this->checkMutualFollow($followerId, $followingId);
            // 创建通知
            $this->createNotification($followingId, $followerId);
            // 提交事务
            $this->db->commit();
            return [
                'success' => true,
                'is_mutual' => $isMutual
            ];
        } catch (Exception $e) {
            $this->db->rollBack();
            return ['error' => $e->getMessage()];
        }
    }
    /**
     * 取消关注
     */
    public function unfollow($followerId, $followingId) {
        try {
            $this->db->beginTransaction();
            // 删除关注记录
            $sql = "DELETE FROM follows WHERE follower_id = ? AND following_id = ?";
            $stmt = $this->db->prepare($sql);
            $stmt->execute([$followerId, $followingId]);
            // 更新计数
            $this->updateCounts($followerId, $followingId, 'unfollow');
            $this->db->commit();
            return ['success' => true];
        } catch (Exception $e) {
            $this->db->rollBack();
            return ['error' => $e->getMessage()];
        }
    }
    /**
     * 获取用户的粉丝列表
     */
    public function getFollowers($userId, $page = 1, $perPage = 20) {
        $offset = ($page - 1) * $perPage;
        $sql = "SELECT u.id, u.username, u.nickname, u.avatar, 
                       f.created_at as followed_at,
                       CASE 
                           WHEN EXISTS (
                               SELECT 1 FROM follows f2 
                               WHERE f2.follower_id = ? 
                               AND f2.following_id = u.id
                           ) THEN 1 ELSE 0 
                       END as is_following_back
                FROM follows f
                INNER JOIN users u ON f.follower_id = u.id
                WHERE f.following_id = ? AND f.status = 1
                ORDER BY f.created_at DESC
                LIMIT ? OFFSET ?";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$userId, $userId, $perPage, $offset]);
        return $stmt->fetchAll();
    }
    /**
     * 获取用户关注的人
     */
    public function getFollowing($userId, $page = 1, $perPage = 20) {
        $offset = ($page - 1) * $perPage;
        $sql = "SELECT u.id, u.username, u.nickname, u.avatar,
                       f.created_at as followed_at,
                       CASE 
                           WHEN EXISTS (
                               SELECT 1 FROM follows f2 
                               WHERE f2.follower_id = u.id 
                               AND f2.following_id = ?
                           ) THEN 1 ELSE 0 
                       END as follows_you
                FROM follows f
                INNER JOIN users u ON f.following_id = u.id
                WHERE f.follower_id = ? AND f.status = 1
                ORDER BY f.created_at DESC
                LIMIT ? OFFSET ?";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$userId, $userId, $perPage, $offset]);
        return $stmt->fetchAll();
    }
    /**
     * 检查是否已关注
     */
    public function isFollowing($followerId, $followingId) {
        $sql = "SELECT 1 FROM follows 
                WHERE follower_id = ? AND following_id = ? AND status = 1";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$followerId, $followingId]);
        return $stmt->rowCount() > 0;
    }
    /**
     * 更新关注计数
     */
    private function updateCounts($followerId, $followingId, $type) {
        if ($type == 'follow') {
            $sql = "UPDATE users 
                    SET followers_count = followers_count + 1 
                    WHERE id = ?";
            $stmt = $this->db->prepare($sql);
            $stmt->execute([$followingId]);
            $sql = "UPDATE users 
                    SET following_count = following_count + 1 
                    WHERE id = ?";
            $stmt = $this->db->prepare($sql);
            $stmt->execute([$followerId]);
        } else {
            // unfollow 时递减
            $sql = "UPDATE users 
                    SET followers_count = GREATEST(followers_count - 1, 0) 
                    WHERE id = ?";
            $stmt = $this->db->prepare($sql);
            $stmt->execute([$followingId]);
            $sql = "UPDATE users 
                    SET following_count = GREATEST(following_count - 1, 0) 
                    WHERE id = ?";
            $stmt = $this->db->prepare($sql);
            $stmt->execute([$followerId]);
        }
    }
    /**
     * 检查互相关注
     */
    private function checkMutualFollow($user1, $user2) {
        $sql = "SELECT 1 FROM follows 
                WHERE (follower_id = ? AND following_id = ?) 
                OR (follower_id = ? AND following_id = ?)
                LIMIT 1";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$user1, $user2, $user2, $user1]);
        return $stmt->rowCount() > 0;
    }
    /**
     * 获取推荐关注
     */
    public function getSuggestions($userId, $limit = 10) {
        // 基于共同关注的人推荐
        $sql = "SELECT u.id, u.username, u.nickname, u.avatar,
                       COUNT(DISTINCT f.following_id) as common_count,
                       u.followers_count
                FROM follows f
                INNER JOIN users u ON f.following_id = u.id
                WHERE f.follower_id IN (
                    SELECT following_id FROM follows WHERE follower_id = ?
                )
                AND f.following_id != ?
                AND f.following_id NOT IN (
                    SELECT following_id FROM follows WHERE follower_id = ?
                )
                GROUP BY u.id
                ORDER BY common_count DESC, u.followers_count DESC
                LIMIT ?";
        $stmt = $this->db->prepare($sql);
        $stmt->execute([$userId, $userId, $userId, $limit]);
        return $stmt->fetchAll();
    }
}

性能优化建议

缓存策略

// 使用 Redis 缓存粉丝数
class FollowCache {
    public function getUserFollowCount($userId) {
        $key = "user:{$userId}:follow_count";
        // 优先从缓存获取
        $count = Redis::get($key);
        if ($count === false) {
            // 从数据库获取并缓存
            $count = DB::table('users')->where('id', $userId)->value('followers_count');
            Redis::setex($key, 3600, $count);
        }
        return $count;
    }
    // 关注操作后更新缓存
    public function invalidateFollowCache($userId) {
        Redis::del("user:{$userId}:follow_count");
        Redis::del("user:{$userId}:following_count");
    }
}

索引优化建议

-- 组合索引优化联合查询
CREATE INDEX idx_follows_user_id ON follows (follower_id, status, created_at);
CREATE INDEX idx_follows_following_id ON follows (following_id, status, created_at);
-- 如果用户量大,考虑使用分区表
-- ALTER TABLE follows PARTITION BY HASH(follower_id) PARTITIONS 10;

注意事项

  1. 并发控制:使用事务和原子操作避免计数不一致
  2. 大V处理:对于有大量粉丝的用户,考虑异步更新计数
  3. 查询优化:使用EXPLAIN分析慢查询,优化索引
  4. 数据归档:定期清理长期不活跃用户的关注关系
  5. 缓存策略:热点数据使用Redis缓存,减少数据库压力

这个设计覆盖了基本的关注/取消关注、互相关注检测、粉丝/关注列表、推荐等核心功能,并考虑了性能优化,根据实际业务需求,你可以适当调整表结构和功能。

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