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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 08:02:56