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

Loopback 4.1.0中Client与Chat模型关系配置及一对一Chat查询需求

解决LoopBack 4中Client与Chat的多对多关系及一对一Chat查询需求

嘿,针对你的LoopBack 4.1.0的需求,我来一步步帮你搞定关系配置和查询功能!

1. 配置多对多关系

首先要明确:一个Chat可以有多个成员,一个Client可以加入多个Chat,这是典型的多对多关系。LoopBack中多对多需要一个中间关联表(through表)来维护两者的绑定关系,我们先完成这部分配置。

这个模型用来存储Chat和Client的关联ID:

// src/models/chat-client-link.model.ts
import {Entity, model, property} from '@loopback/repository';

@model({settings: {strict: false}})
export class ChatClientLink extends Entity {
  @property({
    type: 'string',
    id: true,
    generated: true,
  })
  id?: string;

  @property({
    type: 'string',
    required: true,
  })
  clientId: string; // 关联的Client ID

  @property({
    type: 'string',
    required: true,
  })
  chatId: string; // 关联的Chat ID

  constructor(data?: Partial<ChatClientLink>) {
    super(data);
  }
}

1.2 更新Client模型,添加与Chat的多对多关系

在你的Client模型中,添加hasMany关系指向Chat,并指定中间表:

// src/models/client.model.ts
import {Entity, model, property, hasMany} from '@loopback/repository';
import {Chat} from './chat.model';
import {ChatClientLink} from './chat-client-link.model';

@model()
export class Client extends Entity {
  // 你的现有用户属性(比如用户名、邮箱等)
  @property({
    type: 'string',
    id: true,
    generated: false,
    // 其他配置...
  })
  id: string;

  // 配置多对多关系:一个Client可以关联多个Chat
  @hasMany(() => Chat, {through: {model: () => ChatClientLink}})
  chats: Chat[];

  constructor(data?: Partial<Client>) {
    super(data);
  }
}

1.3 更新Chat模型,添加与Client的多对多关系

同样,在Chat模型中添加反向的hasMany关系,指向Client(也就是Chat的成员):

// src/models/chat.model.ts
import {Entity, model, property, hasMany} from '@loopback/repository';
import {Client} from './client.model';
import {ChatClientLink} from './chat-client-link.model';

@model()
export class Chat extends Entity {
  // 你的现有属性:创建时间、最后消息等
  @property({
    type: 'string',
    id: true,
    generated: false,
  })
  id: string;

  @property({
    type: 'date',
    required: true,
  })
  createdAt: Date;

  @property({
    type: 'string',
  })
  lastMessage?: string;

  // 配置多对多关系:一个Chat可以有多个成员(Client)
  @hasMany(() => Client, {through: {model: () => ChatClientLink}})
  members: Client[];

  constructor(data?: Partial<Chat>) {
    super(data);
  }
}

1.4 生成关联的Repository

运行LoopBack CLI命令生成三个模型对应的Repository:

lb4 repository --model ChatClientLink --datasource db
lb4 repository --model Client --datasource db
lb4 repository --model Chat --datasource db

生成后,确保ChatRepository和ClientRepository中已经正确注入了ChatClientLinkRepository的Getter,这样才能处理多对多关系的查询。

2. 实现查询两个Client间是否存在一对一Chat的功能

这里的“一对一Chat”指的是仅包含当前登录Client和指定Client两个成员的Chat,我们可以在ChatRepository中添加自定义方法实现逻辑,再在Controller中暴露API接口。

2.1 在ChatRepository中添加自定义查询方法

// src/repositories/chat.repository.ts
import {DefaultCrudRepository, repository, HasManyRepositoryFactory} from '@loopback/repository';
import {Chat, ChatRelations, Client} from '../models';
import {DbDataSource} from '../datasources';
import {inject, Getter} from '@loopback/core';
import {ClientRepository} from './client.repository';
import {ChatClientLinkRepository} from './chat-client-link.repository';

export class ChatRepository extends DefaultCrudRepository<
  Chat,
  typeof Chat.prototype.id,
  ChatRelations
> {
  public readonly members: HasManyRepositoryFactory<Client, typeof Chat.prototype.id>;

  constructor(
    @inject('datasources.db') dataSource: DbDataSource,
    @repository.getter('ClientRepository') protected clientRepositoryGetter: Getter<ClientRepository>,
    @repository.getter('ChatClientLinkRepository') protected chatClientLinkRepositoryGetter: Getter<ChatClientLinkRepository>,
  ) {
    super(Chat, dataSource);
    this.members = this.createHasManyRepositoryFactoryFor(
      'members',
      clientRepositoryGetter,
      chatClientLinkRepositoryGetter,
    );
    this.registerInclusionResolver('members', this.members.inclusionResolver);
  }

  // 自定义方法:检查两个Client之间是否存在仅包含他们的Chat
  async checkDirectChatExists(currentClientId: string, targetClientId: string): Promise<boolean> {
    // 1. 找出所有包含其中一个Client的Chat关联记录
    const chatLinks = await (await this.chatClientLinkRepositoryGetter()).find({
      where: {
        or: [
          {clientId: currentClientId},
          {clientId: targetClientId}
        ]
      },
      fields: ['chatId'],
    });

    // 2. 统计每个Chat的成员数量
    const chatMemberCount = new Map<string, number>();
    chatLinks.forEach(link => {
      const count = chatMemberCount.get(link.chatId) || 0;
      chatMemberCount.set(link.chatId, count + 1);
    });

    // 3. 筛选出成员数恰好为2的Chat,验证是否确实包含这两个Client
    for (const [chatId, count] of chatMemberCount.entries()) {
      if (count === 2) {
        const matchedLinks = await (await this.chatClientLinkRepositoryGetter()).find({
          where: {
            chatId,
            or: [
              {clientId: currentClientId},
              {clientId: targetClientId}
            ]
          }
        });
        // 找到两条关联记录,说明这个Chat正好是一对一对话
        if (matchedLinks.length === 2) {
          return true;
        }
      }
    }

    return false;
  }
}

2.2 在Controller中暴露查询接口

创建或更新ChatController,添加API接口,获取当前登录用户ID和目标Client ID,调用自定义方法:

// src/controllers/chat.controller.ts
import {get, param, securityId, SecurityBindings, inject, HttpErrors} from '@loopback/rest';
import {ChatRepository} from '../repositories';
import {authenticate} from '@loopback/authentication';

@authenticate('jwt') // 确保只有登录用户能访问
export class ChatController {
  constructor(
    @repository(ChatRepository)
    public chatRepository: ChatRepository,
    @inject(SecurityBindings.USER) private currentUser: {[securityId]: string},
  ) {}

  @get('/chats/check-direct/{targetClientId}')
  async checkDirectChatExists(
    @param.path.string('targetClientId') targetClientId: string,
  ): Promise<{exists: boolean}> {
    const currentClientId = this.currentUser[securityId];
    
    // 避免查询与自己的对话
    if (currentClientId === targetClientId) {
      throw new HttpErrors.BadRequest('无法查询与自身的对话');
    }

    const exists = await this.chatRepository.checkDirectChatExists(currentClientId, targetClientId);
    return {exists};
  }
}

2.3 配置权限(可选但推荐)

确保接口只有登录用户能访问,这里用了@authenticate('jwt')装饰器,你需要提前配置好LoopBack的JWT认证策略。

测试接口

启动应用后,调用GET /chats/check-direct/{targetClientId}接口,传入目标Client的ID,就能得到是否存在一对一Chat的结果啦!

内容的提问来源于stack exchange,提问作者James Clare

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 03:44:28