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 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-26 05:40:39 -08:00
parent 7ff701369b
commit 1ac183fc0c
4 changed files with 134 additions and 33 deletions

View File

@ -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,

View File

@ -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 {}

View File

@ -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';

View File

@ -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<ITenantFinder>;
tenantFinder?: Type<ITenantFinder>;
/**
* 使 ( useExisting )
* tenantFinder 使
*/
tenantFinderExisting?: Type<ITenantFinder>;
/**
* 使 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> | 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,
};
}
}