From 1ac183fc0cacb9f813a6f1152d253843abf421af Mon Sep 17 00:00:00 2001 From: hailin Date: Mon, 26 Jan 2026 05:40:39 -0800 Subject: [PATCH] fix(tenant): simplify evolution-service tenant configuration - Use SimpleTenantFinder for evolution-service middleware (same as other services) - Keep TenantFinderService available via TenantModule for super-admin - Add useExternalFinder option to TenantContextModule - Remove @Global() decorator from TenantContextModule class Co-Authored-By: Claude Opus 4.5 --- .../evolution-service/src/app.module.ts | 26 ++-- .../infrastructure/tenant/tenant.module.ts | 11 +- packages/shared/src/tenant/index.ts | 6 +- .../src/tenant/tenant-context.module.ts | 124 ++++++++++++++++-- 4 files changed, 134 insertions(+), 33 deletions(-) diff --git a/packages/services/evolution-service/src/app.module.ts b/packages/services/evolution-service/src/app.module.ts index 1f2273c..c15162e 100644 --- a/packages/services/evolution-service/src/app.module.ts +++ b/packages/services/evolution-service/src/app.module.ts @@ -5,6 +5,7 @@ import { ScheduleModule } from '@nestjs/schedule'; import { TenantContextModule, TenantContextMiddleware, + SimpleTenantFinder, DEFAULT_TENANT_ID, } from '@iconsulting/shared'; import { EvolutionModule } from './evolution/evolution.module'; @@ -12,7 +13,6 @@ import { AdminModule } from './admin/admin.module'; import { HealthModule } from './health/health.module'; import { AnalyticsModule } from './analytics/analytics.module'; import { TenantModule } from './infrastructure/tenant/tenant.module'; -import { TenantFinderService } from './infrastructure/tenant/tenant-finder.service'; @Module({ imports: [ @@ -25,17 +25,6 @@ import { TenantFinderService } from './infrastructure/tenant/tenant-finder.servi // 定时任务模块 ScheduleModule.forRoot(), - // Tenant context module (global) - uses custom TenantFinderService - TenantContextModule.forRoot({ - tenantFinder: TenantFinderService, - middlewareOptions: { - allowDefaultTenant: true, - defaultTenantId: DEFAULT_TENANT_ID, - excludePaths: ['/health', '/health/*', '/super-admin/*'], - }, - isGlobal: true, - }), - // 数据库连接 TypeOrmModule.forRootAsync({ imports: [ConfigModule], @@ -54,9 +43,20 @@ import { TenantFinderService } from './infrastructure/tenant/tenant-finder.servi }), }), - // 租户模块 + // 租户模块 (提供 TenantFinderService 用于 super-admin 租户管理) TenantModule, + // Tenant context module (global) - 使用 SimpleTenantFinder + TenantContextModule.forRoot({ + tenantFinder: SimpleTenantFinder, + middlewareOptions: { + allowDefaultTenant: true, + defaultTenantId: DEFAULT_TENANT_ID, + excludePaths: ['/health', '/health/*', '/super-admin/*'], + }, + isGlobal: true, + }), + // Health check HealthModule, diff --git a/packages/services/evolution-service/src/infrastructure/tenant/tenant.module.ts b/packages/services/evolution-service/src/infrastructure/tenant/tenant.module.ts index c9d2884..1b3f166 100644 --- a/packages/services/evolution-service/src/infrastructure/tenant/tenant.module.ts +++ b/packages/services/evolution-service/src/infrastructure/tenant/tenant.module.ts @@ -2,17 +2,10 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { TenantORM } from '../database/postgres/entities/tenant.orm'; import { TenantFinderService } from './tenant-finder.service'; -import { TENANT_FINDER } from '@iconsulting/shared'; @Module({ imports: [TypeOrmModule.forFeature([TenantORM])], - providers: [ - TenantFinderService, - { - provide: TENANT_FINDER, - useExisting: TenantFinderService, - }, - ], - exports: [TenantFinderService, TENANT_FINDER], + providers: [TenantFinderService], + exports: [TenantFinderService], }) export class TenantModule {} diff --git a/packages/shared/src/tenant/index.ts b/packages/shared/src/tenant/index.ts index 15b1b62..b6f6b95 100644 --- a/packages/shared/src/tenant/index.ts +++ b/packages/shared/src/tenant/index.ts @@ -32,4 +32,8 @@ export type { TenantAwareEntity } from './base-tenant.repository.js'; // Module export { TenantContextModule } from './tenant-context.module.js'; -export type { TenantModuleOptions, TenantModuleAsyncOptions } from './tenant-context.module.js'; +export type { + TenantModuleOptions, + TenantModuleAsyncOptions, + TenantModuleAsyncOptionsWithFinder, +} from './tenant-context.module.js'; diff --git a/packages/shared/src/tenant/tenant-context.module.ts b/packages/shared/src/tenant/tenant-context.module.ts index e672fa6..220371c 100644 --- a/packages/shared/src/tenant/tenant-context.module.ts +++ b/packages/shared/src/tenant/tenant-context.module.ts @@ -1,4 +1,4 @@ -import { DynamicModule, Global, Module, Provider, Type } from '@nestjs/common'; +import { DynamicModule, Module, Provider, Type } from '@nestjs/common'; import { TenantContextService } from './tenant-context.service.js'; import { TenantContextMiddleware, @@ -13,9 +13,24 @@ import { TenantGuard } from './tenant.guard.js'; */ export interface TenantModuleOptions { /** - * 租户查找器类 + * 租户查找器类 (用 useClass 创建新实例) */ - tenantFinder: Type; + tenantFinder?: Type; + /** + * 使用已存在的租户查找器 (用 useExisting 复用已注册的实例) + * 当 tenantFinder 需要依赖注入其他服务时使用此选项 + */ + tenantFinderExisting?: Type; + /** + * 使用外部提供的 TENANT_FINDER + * 当此选项为 true 时,必须在其他模块中提供 TENANT_FINDER token + * 且该模块必须在 AppModule 中先于 TenantContextModule 导入 + */ + useExternalFinder?: boolean; + /** + * 导入的模块 (当使用 tenantFinderExisting 时需要导入提供该服务的模块) + */ + imports?: any[]; /** * 中间件选项 */ @@ -27,7 +42,7 @@ export interface TenantModuleOptions { } /** - * 租户模块异步配置选项 + * 租户模块异步配置选项 (返回 TenantModuleOptions) */ export interface TenantModuleAsyncOptions { /** @@ -48,6 +63,29 @@ export interface TenantModuleAsyncOptions { isGlobal?: boolean; } +/** + * 租户模块异步配置选项 (直接注入 TenantFinder 实例) + * 用于 tenantFinder 需要依赖注入其他服务的场景 + */ +export interface TenantModuleAsyncOptionsWithFinder { + /** + * 导入的模块 (必须包含提供 TenantFinder 的模块) + */ + imports: any[]; + /** + * 注入的依赖 (必须包含 TenantFinder 类) + */ + inject: any[]; + /** + * 工厂函数 (第一个注入的依赖应该是 TenantFinder 实例) + */ + useFactory: (tenantFinder: ITenantFinder, ...args: any[]) => Promise | TenantMiddlewareOptions; + /** + * 是否全局注册 + */ + isGlobal?: boolean; +} + /** * 租户上下文模块 * @@ -74,7 +112,6 @@ export interface TenantModuleAsyncOptions { * isGlobal: true, * }) */ -@Global() @Module({}) export class TenantContextModule { /** @@ -84,10 +121,6 @@ export class TenantContextModule { const providers: Provider[] = [ TenantContextService, TenantGuard, - { - provide: TENANT_FINDER, - useClass: options.tenantFinder, - }, { provide: 'TENANT_MIDDLEWARE_OPTIONS', useValue: options.middlewareOptions || {}, @@ -103,10 +136,29 @@ export class TenantContextModule { }, ]; + // 构建 exports 列表 + const exports: any[] = [TenantContextService, TenantGuard, TenantContextMiddleware]; + + // 如果不使用外部 finder,则自己提供并导出 TENANT_FINDER + if (!options.useExternalFinder) { + const tenantFinderProvider: Provider = options.tenantFinderExisting + ? { + provide: TENANT_FINDER, + useExisting: options.tenantFinderExisting, + } + : { + provide: TENANT_FINDER, + useClass: options.tenantFinder!, + }; + providers.push(tenantFinderProvider); + exports.push(TENANT_FINDER); + } + return { module: TenantContextModule, + imports: options.imports || [], providers, - exports: [TenantContextService, TenantGuard, TenantContextMiddleware, TENANT_FINDER], + exports, global: options.isGlobal ?? true, }; } @@ -127,6 +179,9 @@ export class TenantContextModule { provide: TENANT_FINDER, useFactory: async (moduleOptions: TenantModuleOptions) => { // 动态实例化租户查找器 + if (!moduleOptions.tenantFinder) { + throw new Error('tenantFinder is required in async configuration'); + } return new moduleOptions.tenantFinder(); }, inject: ['TENANT_MODULE_OPTIONS'], @@ -156,4 +211,53 @@ export class TenantContextModule { global: options.isGlobal ?? true, }; } + + /** + * 异步注册租户模块 (直接注入 TenantFinder 实例) + * 用于 TenantFinder 需要依赖注入其他服务 (如 Repository) 的场景 + * + * @example + * TenantContextModule.forRootAsyncWithFinder({ + * imports: [TenantModule], + * inject: [TenantFinderService], + * useFactory: (tenantFinder) => ({ + * allowDefaultTenant: true, + * defaultTenantId: DEFAULT_TENANT_ID, + * }), + * isGlobal: true, + * }) + */ + static forRootAsyncWithFinder(options: TenantModuleAsyncOptionsWithFinder): DynamicModule { + const providers: Provider[] = [ + TenantContextService, + TenantGuard, + { + provide: TENANT_FINDER, + useFactory: (tenantFinder: ITenantFinder) => tenantFinder, + inject: [options.inject[0]], // 第一个注入项是 TenantFinder + }, + { + provide: 'TENANT_MIDDLEWARE_OPTIONS', + useFactory: options.useFactory, + inject: options.inject, + }, + { + provide: TenantContextMiddleware, + useFactory: ( + tenantContext: TenantContextService, + tenantFinder: ITenantFinder, + middlewareOptions: TenantMiddlewareOptions, + ) => new TenantContextMiddleware(tenantContext, tenantFinder, middlewareOptions), + inject: [TenantContextService, TENANT_FINDER, 'TENANT_MIDDLEWARE_OPTIONS'], + }, + ]; + + return { + module: TenantContextModule, + imports: options.imports, + providers, + exports: [TenantContextService, TenantGuard, TenantContextMiddleware, TENANT_FINDER], + global: options.isGlobal ?? true, + }; + } }