如何在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
相关产品推荐
相关产品推荐

