You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在GraphQL中构建关注/粉丝模型及帖子点赞点踩功能?

GraphQL 关注/粉丝与帖子点赞功能实现方案

很多教程里把User模型的followers/following设为整数仅用于演示,实际生产中需要通过关联表来存储用户间的关注关系,以下是具体实现方案:

一、构建关注/粉丝模型

1. 数据库层设计

需要创建一个中间关联表(例如命名为follow_relationships),用来存储关注者与被关注者的关联关系,表结构如下:

  • follower_id: 关注者的用户ID(外键关联用户表)
  • following_id: 被关注者的用户ID(外键关联用户表)
  • created_at: 关注操作的时间戳

将follower_id与following_id设为联合主键,避免同一用户重复关注同一对象。

2. GraphQL Schema 定义

在Schema中扩展User类型,添加粉丝/关注列表、计数字段,同时定义关注/取消关注的Mutation:

type User {
  id: ID!
  name: String!
  screenName: String!
  # 该用户的粉丝列表
  followers: [User!]!
  # 该用户关注的列表
  following: [User!]!
  # 粉丝总数
  followersCount: Int!
  # 关注总数
  followingCount: Int!
}

type Mutation {
  # 关注指定用户
  followUser(followingId: ID!): Boolean!
  # 取消关注指定用户
  unfollowUser(followingId: ID!): Boolean!
}

3. Resolver 实现(以 Node.js + Knex 为例)

Resolver负责处理数据的查询与修改逻辑:

const resolvers = {
  User: {
    // 查询当前用户的粉丝列表
    async followers(parent, _, { db }) {
      const followerIds = await db('follow_relationships')
        .where('following_id', parent.id)
        .pluck('follower_id');
      return db('users').whereIn('id', followerIds);
    },
    // 查询当前用户关注的列表
    async following(parent, _, { db }) {
      const followingIds = await db('follow_relationships')
        .where('follower_id', parent.id)
        .pluck('following_id');
      return db('users').whereIn('id', followingIds);
    },
    // 统计粉丝数量
    async followersCount(parent, _, { db }) {
      const countResult = await db('follow_relationships')
        .where('following_id', parent.id)
        .count('id as count');
      return parseInt(countResult[0].count);
    },
    // 统计关注数量
    async followingCount(parent, _, { db }) {
      const countResult = await db('follow_relationships')
        .where('follower_id', parent.id)
        .count('id as count');
      return parseInt(countResult[0].count);
    }
  },
  Mutation: {
    // 执行关注操作
    async followUser(_, { followingId }, { db, currentUserId }) {
      // 检查是否已关注过该用户
      const existingFollow = await db('follow_relationships')
        .where({ follower_id: currentUserId, following_id: followingId })
        .first();
      if (existingFollow) return false;
      
      // 添加关注记录
      await db('follow_relationships').insert({
        follower_id: currentUserId,
        following_id: followingId,
        created_at: new Date()
      });
      return true;
    },
    // 执行取消关注操作
    async unfollowUser(_, { followingId }, { db, currentUserId }) {
      // 删除对应的关注记录
      const deletedRows = await db('follow_relationships')
        .where({ follower_id: currentUserId, following_id: followingId })
        .del();
      return deletedRows > 0;
    }
  }
};

二、实现帖子点赞/点踩功能

1. 数据库层设计

创建关联表post_reactions存储用户对帖子的反应,表结构如下:

  • user_id: 操作用户的ID(外键关联用户表)
  • post_id: 目标帖子的ID(外键关联帖子表)
  • reaction_type: 反应类型(枚举值:LIKE/DISLIKE)
  • created_at: 操作时间戳

将user_id与post_id设为联合主键,避免同一用户重复对同一帖子进行反应。

2. GraphQL Schema 定义

扩展Post类型,添加点赞/点踩相关字段,同时定义反应操作的Mutation:

enum ReactionType {
  LIKE
  DISLIKE
}

type Post {
  id: ID!
  content: String!
  author: User!
  # 点赞该帖子的用户列表
  likes: [User!]!
  # 点踩该帖子的用户列表
  dislikes: [User!]!
  # 点赞总数
  likeCount: Int!
  # 点踩总数
  dislikeCount: Int!
  # 当前登录用户对该帖子的反应(用于前端显示状态)
  currentUserReaction: ReactionType
}

type Mutation {
  # 添加/切换帖子反应
  addPostReaction(postId: ID!, reactionType: ReactionType!): Boolean!
  # 取消帖子反应
  removePostReaction(postId: ID!): Boolean!
}

3. Resolver 实现(以 Node.js + Knex 为例)

const resolvers = {
  Post: {
    // 查询点赞该帖子的用户列表
    async likes(parent, _, { db }) {
      const userIds = await db('post_reactions')
        .where({ post_id: parent.id, reaction_type: 'LIKE' })
        .pluck('user_id');
      return db('users').whereIn('id', userIds);
    },
    // 查询点踩该帖子的用户列表
    async dislikes(parent, _, { db }) {
      const userIds = await db('post_reactions')
        .where({ post_id: parent.id, reaction_type: 'DISLIKE' })
        .pluck('user_id');
      return db('users').whereIn('id', userIds);
    },
    // 统计点赞数量
    async likeCount(parent, _, { db }) {
      const countResult = await db('post_reactions')
        .where({ post_id: parent.id, reaction_type: 'LIKE' })
        .count('id as count');
      return parseInt(countResult[0].count);
    },
    // 统计点踩数量
    async dislikeCount(parent, _, { db }) {
      const countResult = await db('post_reactions')
        .where({ post_id: parent.id, reaction_type: 'DISLIKE' })
        .count('id as count');
      return parseInt(countResult[0].count);
    },
    // 查询当前用户对该帖子的反应
    async currentUserReaction(parent, _, { db, currentUserId }) {
      if (!currentUserId) return null;
      const reaction = await db('post_reactions')
        .where({ post_id: parent.id, user_id: currentUserId })
        .first();
      return reaction?.reaction_type || null;
    }
  },
  Mutation: {
    // 添加或切换反应
    async addPostReaction(_, { postId, reactionType }, { db, currentUserId }) {
      if (!currentUserId) throw new Error('请先登录');
      
      const existingReaction = await db('post_reactions')
        .where({ user_id: currentUserId, post_id: postId })
        .first();
      
      if (existingReaction) {
        if (existingReaction.reaction_type === reactionType) {
          // 已有相同反应,执行取消操作
          await db('post_reactions')
            .where({ user_id: currentUserId, post_id: postId })
            .del();
          return false;
        } else {
          // 切换反应类型
          await db('post_reactions')
            .where({ user_id: currentUserId, post_id: postId })
            .update({ reaction_type: reactionType });
          return true;
        }
      } else {
        // 添加新反应
        await db('post_reactions').insert({
          user_id: currentUserId,
          post_id: postId,
          reaction_type: reactionType,
          created_at: new Date()
        });
        return true;
      }
    },
    // 取消反应
    async removePostReaction(_, { postId }, { db, currentUserId }) {
      if (!currentUserId) throw new Error('请先登录');
      
      const deletedRows = await db('post_reactions')
        .where({ user_id: currentUserId, post_id: postId })
        .del();
      return deletedRows > 0;
    }
  }
};

内容的提问来源于stack exchange,提问作者the coder

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 05:55:23