Loopback 4.1.0中Client与Chat模型关系配置及一对一Chat查询需求
嘿,针对你的LoopBack 4.1.0的需求,我来一步步帮你搞定关系配置和查询功能!
1. 配置多对多关系
首先要明确:一个Chat可以有多个成员,一个Client可以加入多个Chat,这是典型的多对多关系。LoopBack中多对多需要一个中间关联表(through表)来维护两者的绑定关系,我们先完成这部分配置。
1.1 创建中间关联模型 ChatClientLink
这个模型用来存储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

