如何在Prisma ORM Schema中实现互关式好友关系?
基于Prisma的用户互关好友关系实现方案
初始的用户订阅/关注方案
我最初采用了多对多自关联的用户关联方案:
datasource db { provider = "mongodb" url = env("DATABASE_URL") } generator client { provider = "prisma-client-js" } model User { id String @id @default(auto()) @map("_id") @db.ObjectId followers User[] @relation("UserFollows", fields: [followerIds], references: [id]) followerIds String[] @map("follower_ids") @db.ObjectId subscriptions User[] @relation("UserFollows", fields: [subscriptionIds], references: [id]) subscriptionIds String[] @map("subscription_ids") @db.ObjectId }
维护这种订阅关系很简单:
export class UsersService { constructor(private readonly prisma: PrismaService) {} async subscribeUser(followerId: string, subscriptionId: string) { await this.prisma.user.update({ where: { id: followerId }, data: { subscriptions: { connect: { id: subscriptionId } } }, }); return { message: 'User successfully subscribed', }; } }
核心问题
在上述方案基础上,如何实现并维护互关的好友关系?怎样避免通过频繁计算数组交集来判断好友身份?
解决方案
方案1:新增独立好友关联字段
直接在User模型中添加好友相关字段,当双向订阅完成时自动同步好友列表,避免每次查询都计算交集。
修改后的Prisma模型
model User { id String @id @default(auto()) @map("_id") @db.ObjectId followers User[] @relation("UserFollows", fields: [followerIds], references: [id]) followerIds String[] @map("follower_ids") @db.ObjectId subscriptions User[] @relation("UserFollows", fields: [subscriptionIds], references: [id]) subscriptionIds String[] @map("subscription_ids") @db.ObjectId // 新增好友关联字段 friends User[] @relation("UserFriends", fields: [friendIds], references: [id]) friendIds String[] @map("friend_ids") @db.ObjectId }
维护逻辑调整
修改订阅方法,完成订阅后检查对方是否已反向订阅,若是则双向添加好友:
async subscribeUser(followerId: string, subscriptionId: string) { // 执行订阅操作 await this.prisma.user.update({ where: { id: followerId }, data: { subscriptions: { connect: { id: subscriptionId } } }, }); // 检查目标用户是否已订阅当前用户 const targetUser = await this.prisma.user.findUnique({ where: { id: subscriptionId }, select: { followerIds: true } }); if (targetUser?.followerIds.includes(followerId)) { // 双向订阅完成,互相添加为好友 await Promise.all([ this.prisma.user.update({ where: { id: followerId }, data: { friends: { connect: { id: subscriptionId } } } }), this.prisma.user.update({ where: { id: subscriptionId }, data: { friends: { connect: { id: followerId } } } }) ]); } return { message: 'User successfully subscribed' }; }
同时补充取消订阅的逻辑,若取消后不再互关则移除好友关系:
async unsubscribeUser(followerId: string, subscriptionId: string) { await this.prisma.user.update({ where: { id: followerId }, data: { subscriptions: { disconnect: { id: subscriptionId } } }, }); // 检查目标用户是否仍订阅当前用户 const targetUser = await this.prisma.user.findUnique({ where: { id: subscriptionId }, select: { subscriptionIds: true } }); if (!targetUser?.subscriptionIds.includes(followerId)) { // 不再互关,双向移除好友 await Promise.all([ this.prisma.user.update({ where: { id: followerId }, data: { friends: { disconnect: { id: subscriptionId } } } }), this.prisma.user.update({ where: { id: subscriptionId }, data: { friends: { disconnect: { id: followerId } } } }) ]); } return { message: 'User successfully unsubscribed' }; }
方案2:使用中间表存储好友关系
若需要记录好友关系的额外信息(如添加时间、备注),可创建独立的Friendship中间表,替代数组字段,扩展性更强。
Prisma模型定义
model User { id String @id @default(auto()) @map("_id") @db.ObjectId followers User[] @relation("UserFollows", fields: [followerIds], references: [id]) followerIds String[] @map("follower_ids") @db.ObjectId subscriptions User[] @relation("UserFollows", fields: [subscriptionIds], references: [id]) subscriptionIds String[] @map("subscription_ids") @db.ObjectId friendships Friendship[] } model Friendship { id String @id @default(auto()) @map("_id") @db.ObjectId userId String @db.ObjectId friendId String @db.ObjectId createdAt DateTime @default(now()) note String? // 可添加备注等自定义字段 user User @relation(fields: [userId], references: [id]) friend User @relation(fields: [friendId], references: [id]) // 唯一约束,避免重复创建好友关系 @@unique([userId, friendId]) }
维护逻辑
订阅时检查双向关系,创建/删除好友记录:
async subscribeUser(followerId: string, subscriptionId: string) { await this.prisma.user.update({ where: { id: followerId }, data: { subscriptions: { connect: { id: subscriptionId } } }, }); const targetUser = await this.prisma.user.findUnique({ where: { id: subscriptionId }, select: { subscriptionIds: true } }); if (targetUser?.subscriptionIds.includes(followerId)) { // 创建双向好友关系(跳过重复记录) await this.prisma.friendship.createMany({ data: [ { userId: followerId, friendId: subscriptionId }, { userId: subscriptionId, friendId: followerId } ], skipDuplicates: true }); } return { message: 'User successfully subscribed' }; } async unsubscribeUser(followerId: string, subscriptionId: string) { await this.prisma.user.update({ where: { id: followerId }, data: { subscriptions: { disconnect: { id: subscriptionId } } }, }); const targetUser = await this.prisma.user.findUnique({ where: { id: subscriptionId }, select: { subscriptionIds: true } }); if (!targetUser?.subscriptionIds.includes(followerId)) { // 删除双向好友关系 await this.prisma.friendship.deleteMany({ where: { OR: [ { userId: followerId, friendId: subscriptionId }, { userId: subscriptionId, friendId: followerId } ] } }); } return { message: 'User successfully unsubscribed' }; }
方案优势
- 无需每次查询都计算
followerIds与subscriptionIds的交集,直接查询好友字段或中间表即可,性能更优。 - 好友关系状态预计算完成,业务代码可直接使用,避免重复逻辑。
- 中间表方案支持扩展好友关系的额外属性,适配更复杂的业务需求。
内容的提问来源于stack exchange,提问作者Artyom Ionash
相关产品推荐
相关产品推荐

