From 7ff701369b31a44aa8990efab385ea2f1d3f4378 Mon Sep 17 00:00:00 2001 From: hailin Date: Mon, 26 Jan 2026 04:31:56 -0800 Subject: [PATCH] fix(tenant): use TenantContextModule.forRoot() for global tenant context All services were providing TenantContextService directly without making it global, causing DI resolution failures in child modules. Now using TenantContextModule.forRoot() which exports TenantContextService globally so all repositories can access it. Co-Authored-By: Claude Opus 4.5 --- .builder.Dockerfile | 13 ++++++++ .claude/settings.local.json | 16 +++++++++- .../conversation-service/src/app.module.ts | 29 +++++++++--------- .../evolution-service/src/app.module.ts | 30 +++++++++---------- .../services/file-service/src/app.module.ts | 29 +++++++++--------- .../knowledge-service/src/app.module.ts | 29 +++++++++--------- .../payment-service/src/app.module.ts | 29 +++++++++--------- .../services/user-service/src/app.module.ts | 29 +++++++++--------- 8 files changed, 112 insertions(+), 92 deletions(-) create mode 100644 .builder.Dockerfile diff --git a/.builder.Dockerfile b/.builder.Dockerfile new file mode 100644 index 0000000..36bb508 --- /dev/null +++ b/.builder.Dockerfile @@ -0,0 +1,13 @@ +FROM node:20-alpine + +# 安装 pnpm +RUN corepack enable && corepack prepare pnpm@latest --activate + +# 设置工作目录 +WORKDIR /app + +# 设置 pnpm store 目录 +ENV PNPM_HOME=/root/.local/share/pnpm +ENV PATH=$PNPM_HOME:$PATH + +CMD ["sh"] diff --git a/.claude/settings.local.json b/.claude/settings.local.json index d2ac671..1eee7b5 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -28,7 +28,21 @@ "Bash(cmd /c \"dir %USERPROFILE%\\\\.ssh\")", "Bash(git fetch:*)", "Bash(TEST_USER_ID=\"a1b2c3d4-e5f6-7890-abcd-ef1234567890\":*)", - "Bash(git reset:*)" + "Bash(git reset:*)", + "Bash(ls \"c:\\\\Users\\\\dong\\\\Desktop\\\\iConsulting\\\\packages\\\\services\\\\evolution-service\\\\src\\\\infrastructure\\\\database\\\\entities\"\" 2>/dev/null || echo \"Directory empty or not exists \")", + "Bash(done)", + "Bash(for service in knowledge-service conversation-service user-service payment-service evolution-service file-service)", + "Bash(do echo \"=== $service ===\" find \"/c/Users/dong/Desktop/iConsulting/packages/services/$service/src\" -type d)", + "Bash(pnpm build)", + "Bash(find:*)", + "Bash(grep:*)", + "Bash(timeout:*)", + "Bash(git restore:*)", + "Bash(pnpm build:*)", + "Bash(pnpm --filter @iconsulting/shared build:*)", + "Bash(pnpm --filter @iconsulting/user-service build:*)", + "Bash(pnpm --filter @iconsulting/conversation-service build:*)", + "Bash(pnpm --filter @iconsulting/payment-service build:*)" ] } } diff --git a/packages/services/conversation-service/src/app.module.ts b/packages/services/conversation-service/src/app.module.ts index 068c34d..88c1b1b 100644 --- a/packages/services/conversation-service/src/app.module.ts +++ b/packages/services/conversation-service/src/app.module.ts @@ -2,7 +2,7 @@ import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/c import { ConfigModule, ConfigService } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; import { - TenantContextService, + TenantContextModule, TenantContextMiddleware, SimpleTenantFinder, DEFAULT_TENANT_ID, @@ -19,6 +19,17 @@ import { HealthModule } from './health/health.module'; envFilePath: ['.env.local', '.env'], }), + // Tenant context module (global) + TenantContextModule.forRoot({ + tenantFinder: SimpleTenantFinder, + middlewareOptions: { + allowDefaultTenant: true, + defaultTenantId: DEFAULT_TENANT_ID, + excludePaths: ['/health', '/health/*'], + }, + isGlobal: true, + }), + // Database TypeOrmModule.forRootAsync({ imports: [ConfigModule], @@ -53,27 +64,15 @@ import { HealthModule } from './health/health.module'; ConversationModule, ClaudeModule, ], - providers: [TenantContextService, SimpleTenantFinder], }) export class AppModule implements NestModule { constructor( - private readonly tenantContext: TenantContextService, - private readonly tenantFinder: SimpleTenantFinder, + private readonly tenantMiddleware: TenantContextMiddleware, ) {} configure(consumer: MiddlewareConsumer) { - const tenantMiddleware = new TenantContextMiddleware( - this.tenantContext, - this.tenantFinder, - { - allowDefaultTenant: true, - defaultTenantId: DEFAULT_TENANT_ID, - excludePaths: ['/health', '/health/*'], - }, - ); - consumer - .apply((req: any, res: any, next: any) => tenantMiddleware.use(req, res, next)) + .apply((req: any, res: any, next: any) => this.tenantMiddleware.use(req, res, next)) .exclude( { path: 'health', method: RequestMethod.GET }, { path: 'health/(.*)', method: RequestMethod.ALL }, diff --git a/packages/services/evolution-service/src/app.module.ts b/packages/services/evolution-service/src/app.module.ts index 41a79a9..1f2273c 100644 --- a/packages/services/evolution-service/src/app.module.ts +++ b/packages/services/evolution-service/src/app.module.ts @@ -3,7 +3,7 @@ import { ConfigModule, ConfigService } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ScheduleModule } from '@nestjs/schedule'; import { - TenantContextService, + TenantContextModule, TenantContextMiddleware, DEFAULT_TENANT_ID, } from '@iconsulting/shared'; @@ -25,6 +25,17 @@ 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,28 +65,15 @@ import { TenantFinderService } from './infrastructure/tenant/tenant-finder.servi AdminModule, AnalyticsModule, ], - providers: [TenantContextService], }) export class AppModule implements NestModule { constructor( - private readonly tenantContext: TenantContextService, - private readonly tenantFinder: TenantFinderService, + private readonly tenantMiddleware: TenantContextMiddleware, ) {} configure(consumer: MiddlewareConsumer) { - // 创建租户中间件 - const tenantMiddleware = new TenantContextMiddleware( - this.tenantContext, - this.tenantFinder, - { - allowDefaultTenant: true, - defaultTenantId: DEFAULT_TENANT_ID, - excludePaths: ['/health', '/health/*', '/super-admin/*'], - }, - ); - consumer - .apply((req: any, res: any, next: any) => tenantMiddleware.use(req, res, next)) + .apply((req: any, res: any, next: any) => this.tenantMiddleware.use(req, res, next)) .exclude( { path: 'health', method: RequestMethod.GET }, { path: 'health/(.*)', method: RequestMethod.ALL }, diff --git a/packages/services/file-service/src/app.module.ts b/packages/services/file-service/src/app.module.ts index 9e4cc5e..aa94c65 100644 --- a/packages/services/file-service/src/app.module.ts +++ b/packages/services/file-service/src/app.module.ts @@ -2,7 +2,7 @@ import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/c import { ConfigModule, ConfigService } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; import { - TenantContextService, + TenantContextModule, TenantContextMiddleware, SimpleTenantFinder, DEFAULT_TENANT_ID, @@ -18,6 +18,17 @@ import { FileModule } from './file/file.module'; envFilePath: ['.env.local', '.env'], }), + // Tenant context module (global) + TenantContextModule.forRoot({ + tenantFinder: SimpleTenantFinder, + middlewareOptions: { + allowDefaultTenant: true, + defaultTenantId: DEFAULT_TENANT_ID, + excludePaths: ['/health', '/health/*'], + }, + isGlobal: true, + }), + // 数据库连接 TypeOrmModule.forRootAsync({ imports: [ConfigModule], @@ -41,27 +52,15 @@ import { FileModule } from './file/file.module'; // 功能模块 FileModule, ], - providers: [TenantContextService, SimpleTenantFinder], }) export class AppModule implements NestModule { constructor( - private readonly tenantContext: TenantContextService, - private readonly tenantFinder: SimpleTenantFinder, + private readonly tenantMiddleware: TenantContextMiddleware, ) {} configure(consumer: MiddlewareConsumer) { - const tenantMiddleware = new TenantContextMiddleware( - this.tenantContext, - this.tenantFinder, - { - allowDefaultTenant: true, - defaultTenantId: DEFAULT_TENANT_ID, - excludePaths: ['/health', '/health/*'], - }, - ); - consumer - .apply((req: any, res: any, next: any) => tenantMiddleware.use(req, res, next)) + .apply((req: any, res: any, next: any) => this.tenantMiddleware.use(req, res, next)) .exclude( { path: 'health', method: RequestMethod.GET }, { path: 'health/(.*)', method: RequestMethod.ALL }, diff --git a/packages/services/knowledge-service/src/app.module.ts b/packages/services/knowledge-service/src/app.module.ts index 1772204..1f1a9eb 100644 --- a/packages/services/knowledge-service/src/app.module.ts +++ b/packages/services/knowledge-service/src/app.module.ts @@ -2,7 +2,7 @@ import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/c import { ConfigModule, ConfigService } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; import { - TenantContextService, + TenantContextModule, TenantContextMiddleware, SimpleTenantFinder, DEFAULT_TENANT_ID, @@ -19,6 +19,17 @@ import { HealthModule } from './health/health.module'; envFilePath: ['.env.local', '.env'], }), + // Tenant context module (global) + TenantContextModule.forRoot({ + tenantFinder: SimpleTenantFinder, + middlewareOptions: { + allowDefaultTenant: true, + defaultTenantId: DEFAULT_TENANT_ID, + excludePaths: ['/health', '/health/*'], + }, + isGlobal: true, + }), + // 数据库连接 TypeOrmModule.forRootAsync({ imports: [ConfigModule], @@ -44,27 +55,15 @@ import { HealthModule } from './health/health.module'; KnowledgeModule, MemoryModule, ], - providers: [TenantContextService, SimpleTenantFinder], }) export class AppModule implements NestModule { constructor( - private readonly tenantContext: TenantContextService, - private readonly tenantFinder: SimpleTenantFinder, + private readonly tenantMiddleware: TenantContextMiddleware, ) {} configure(consumer: MiddlewareConsumer) { - const tenantMiddleware = new TenantContextMiddleware( - this.tenantContext, - this.tenantFinder, - { - allowDefaultTenant: true, - defaultTenantId: DEFAULT_TENANT_ID, - excludePaths: ['/health', '/health/*'], - }, - ); - consumer - .apply((req: any, res: any, next: any) => tenantMiddleware.use(req, res, next)) + .apply((req: any, res: any, next: any) => this.tenantMiddleware.use(req, res, next)) .exclude( { path: 'health', method: RequestMethod.GET }, { path: 'health/(.*)', method: RequestMethod.ALL }, diff --git a/packages/services/payment-service/src/app.module.ts b/packages/services/payment-service/src/app.module.ts index 5fefe31..bec7c06 100644 --- a/packages/services/payment-service/src/app.module.ts +++ b/packages/services/payment-service/src/app.module.ts @@ -2,7 +2,7 @@ import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/c import { ConfigModule, ConfigService } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; import { - TenantContextService, + TenantContextModule, TenantContextMiddleware, SimpleTenantFinder, DEFAULT_TENANT_ID, @@ -18,6 +18,17 @@ import { HealthModule } from './health/health.module'; envFilePath: ['.env.local', '.env'], }), + // Tenant context module (global) + TenantContextModule.forRoot({ + tenantFinder: SimpleTenantFinder, + middlewareOptions: { + allowDefaultTenant: true, + defaultTenantId: DEFAULT_TENANT_ID, + excludePaths: ['/health', '/health/*'], + }, + isGlobal: true, + }), + TypeOrmModule.forRootAsync({ imports: [ConfigModule], inject: [ConfigService], @@ -56,27 +67,15 @@ import { HealthModule } from './health/health.module'; OrderModule, PaymentModule, ], - providers: [TenantContextService, SimpleTenantFinder], }) export class AppModule implements NestModule { constructor( - private readonly tenantContext: TenantContextService, - private readonly tenantFinder: SimpleTenantFinder, + private readonly tenantMiddleware: TenantContextMiddleware, ) {} configure(consumer: MiddlewareConsumer) { - const tenantMiddleware = new TenantContextMiddleware( - this.tenantContext, - this.tenantFinder, - { - allowDefaultTenant: true, - defaultTenantId: DEFAULT_TENANT_ID, - excludePaths: ['/health', '/health/*'], - }, - ); - consumer - .apply((req: any, res: any, next: any) => tenantMiddleware.use(req, res, next)) + .apply((req: any, res: any, next: any) => this.tenantMiddleware.use(req, res, next)) .exclude( { path: 'health', method: RequestMethod.GET }, { path: 'health/(.*)', method: RequestMethod.ALL }, diff --git a/packages/services/user-service/src/app.module.ts b/packages/services/user-service/src/app.module.ts index 1fac6e4..4373847 100644 --- a/packages/services/user-service/src/app.module.ts +++ b/packages/services/user-service/src/app.module.ts @@ -3,7 +3,7 @@ import { ConfigModule, ConfigService } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; import { JwtModule } from '@nestjs/jwt'; import { - TenantContextService, + TenantContextModule, TenantContextMiddleware, SimpleTenantFinder, DEFAULT_TENANT_ID, @@ -19,6 +19,17 @@ import { HealthModule } from './health/health.module'; envFilePath: ['.env.local', '.env'], }), + // Tenant context module (global) + TenantContextModule.forRoot({ + tenantFinder: SimpleTenantFinder, + middlewareOptions: { + allowDefaultTenant: true, + defaultTenantId: DEFAULT_TENANT_ID, + excludePaths: ['/health', '/health/*'], + }, + isGlobal: true, + }), + TypeOrmModule.forRootAsync({ imports: [ConfigModule], inject: [ConfigService], @@ -52,27 +63,15 @@ import { HealthModule } from './health/health.module'; UserModule, AuthModule, ], - providers: [TenantContextService, SimpleTenantFinder], }) export class AppModule implements NestModule { constructor( - private readonly tenantContext: TenantContextService, - private readonly tenantFinder: SimpleTenantFinder, + private readonly tenantMiddleware: TenantContextMiddleware, ) {} configure(consumer: MiddlewareConsumer) { - const tenantMiddleware = new TenantContextMiddleware( - this.tenantContext, - this.tenantFinder, - { - allowDefaultTenant: true, - defaultTenantId: DEFAULT_TENANT_ID, - excludePaths: ['/health', '/health/*'], - }, - ); - consumer - .apply((req: any, res: any, next: any) => tenantMiddleware.use(req, res, next)) + .apply((req: any, res: any, next: any) => this.tenantMiddleware.use(req, res, next)) .exclude( { path: 'health', method: RequestMethod.GET }, { path: 'health/(.*)', method: RequestMethod.ALL },