Nestjs框架: 基于Mongodb的多租户功能集成和优化

Nestjs框架: 数据库多租户模式与动态模块初探 本文探讨了如何通过动态模块和依赖注入实现多租户数据库系统的集成。文章首先概述了TypeORM、Prisma和Mongoose三种ORM框架的基本连接方式,重点分析了Prisma在多库场景下的动态配置需求。通过研究Prisma的文档,提出了通过程序化覆盖数据源URL的方案,将连接信息拆分为独立字段管理。随后深入剖析了NestJS动态模块的实现原理,通过分析TypeORM模块源码和官方文档示例,展示了如何通过forRootAsync方法实现配置的动态注入。最后,文章通过一个多租户场景案例,阐述了如何利用动态模块 阅读详情

概述

  • 基于前文,我们知道如何集成多租户的相关功能了, 现在我们继续集成Monodb的多租户形式
  • 需要注意的是,MongoDB 在 NestJS 中的使用过程中存在一些“坑点”
  • 如果按照默认方式集成,会发现连接数在不断增长,即使我们请求的是相同的数据库地址和租户信息
  • 这说明每次接口请求都在创建新的数据库连接,而不是复用已有连接,这个行为与我们预期不符
  • 数据库连接是有限资源,频繁创建连接会导致 IO 资源耗尽、性能下降等问题
  • 下面,我们分别来看看如何解决此类问题

开始集成


1 ) 编写 docker-compose.multi.yaml 文件

services:
  mongo:
    image: mongo:8  # 使用最新的 MongoDB 镜像
    container_name: mongo_app
    restart: always
    ports:
      - "27018:27017"  # 将容器的 27017 端口映射到主机的 27017 端口
    environment:
      - MONGO_INITDB_ROOT_USERNAME=root  # 设置 MongoDB 的 root 用户名
      - MONGO_INITDB_ROOT_PASSWORD=123456_mongodb  # 设置 MongoDB 的 root 密码
      # 调整日志级别的例子,可选值如 "0"(致命错误)、"1"(警告+错误)、"2"(信息+警告+错误)...
      - MONGO_LOG_LEVEL=2
      - MONGO_SYSTEM_LOG_PATH=/data/logs/mongodb.log
    volumes:
      - ./docker-dbconfig/mongo/conf/mongod.conf:/etc/mongod.conf
      - ./docker-dbconfig/mongo/data/db:/data/db  # 将容器内的 /data/db 目录映射到本地的 ./data/db 目录,用于数据持久化
      - ./docker-dbconfig/mongo/logs:/data/logs
    networks:
      - light_network

  mongo-express:
    image: mongo-express:latest
    container_name: mongo_express_app
    restart: always
    environment:
      ME_CONFIG_MONGODB_ADMINUSERNAME: root
      ME_CONFIG_MONGODB_ADMINPASSWORD: 123456_mongodb
      ME_CONFIG_MONGODB_URL: mongodb://root:123456_mongodb@mongo:27017/
      ME_CONFIG_BASICAUTH: false
    ports:
      - 18081:8081
    networks:
      - light_network

  mongo2:
    image: mongo:8  # 使用最新的 MongoDB 镜像
    container_name: mongo_app2
    restart: always
    ports:
      - "27019:27017"  # 将容器的 27017 端口映射到主机的 27017 端口
    environment:
      - MONGO_INITDB_ROOT_USERNAME=root  # 设置 MongoDB 的 root 用户名
      - MONGO_INITDB_ROOT_PASSWORD=123456_mongodb  # 设置 MongoDB 的 root 密码
      # 调整日志级别的例子,可选值如 "0"(致命错误)、"1"(警告+错误)、"2"(信息+警告+错误)...
      - MONGO_LOG_LEVEL=2
      - MONGO_SYSTEM_LOG_PATH=/data/logs/mongodb.log
    volumes:
      - ./docker-dbconfig/mongo2/conf/mongod.conf:/etc/mongod.conf
      - ./docker-dbconfig/mongo2/data/db:/data/db  # 将容器内的 /data/db 目录映射到本地的 ./data/db 目录,用于数据持久化
      - ./docker-dbconfig/mongo2/logs:/data/logs
    networks:
      - light_network

  mongo-express2:
    image: mongo-express:latest
    container_name: mongo_express_app2
    restart: always
    environment:
      ME_CONFIG_MONGODB_ADMINUSERNAME: root
      ME_CONFIG_MONGODB_ADMINPASSWORD: 123456_mongodb
      ME_CONFIG_MONGODB_URL: mongodb://root:123456_mongodb@mongo2:27017/
      ME_CONFIG_BASICAUTH: false
    ports:
      - 18082:8081
    networks:
      - light_network

networks:
  light_network:
    external: true
  • 启动服务,之后在 UI 管理界面给 2个数据库加入可识别的数据
  • 下面测试的时候,会看到数据

2 ) 配置 .env

T1_MONGODB_URI="mongodb://root:123456_mongodb@localhost:27018/nestmongodb"
T2_MONGODB_URI="mongodb://root:123456_mongodb@localhost:27019/nestmongodb"

3 ) 编写 database/mongoose/mongoose.constant.ts

// 这个配置模拟调接口/读数据库获取的
export const tenantMap = new Map([
	['1', 'T1'],
	['2', 'T2']
]);
export const defaultTenant = tenantMap.values().next().value; // 第一个

4 ) 编写 database/mongoose/mongoose-config.service.ts

import { Inject } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import {
	MongooseModuleOptions,
	MongooseOptionsFactory,
} from '@nestjs/mongoose';
import { Request } from 'express';
import { tenantMap, defaultTenant } from './mongoose.constant';
import { ConfigService } from '@nestjs/config';

export class MongooseConfigService implements MongooseOptionsFactory {
	constructor(
		@Inject(REQUEST) private request: Request,
		private configService: ConfigService,
	) {}
	
	createMongooseOptions():
		| MongooseModuleOptions
		| Promise<MongooseModuleOptions> {
			const headers = this.request.headers;
			const tenantId = headers['x-tenant-id'] as string;
			console.log('tenantId: ', tenantId)
			if (tenantId && !tenantMap.has(tenantId)) {
				throw new Error('invalid tenantId');
			}
			const t_prefix = !tenantId ? defaultTenant : tenantMap.get(tenantId);
			const uri = this.configService.get<string>(`${t_prefix}_MONGODB_URI`);
			return { uri } as MongooseModuleOptions;
		}
}

5 ) 编写 app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { MongooseModule } from '@nestjs/mongoose';
import { UserSchema } from './user/user.schema'
import { ConfigModule, ConfigService } from '@nestjs/config';
import { MongooseConfigService } from './database/mongoose/mongoose-config.service';

@Module({
  imports: [
    // 1. 下面这个后续可以封装一个新的模块,来匹配 .env 和 其他配置
    ConfigModule.forRoot({  // 配置环境变量模块
      envFilePath: '.env', // 指定环境变量文件路径
      isGlobal: true, // 全局可用
    }),

    MongooseModule.forRootAsync({
      useClass: MongooseConfigService,
    }),
    MongooseModule.forFeature([{ name: 'User', schema: UserSchema }]),
  ],
  controllers: [AppController],
  providers: []
})

export class AppModule {}

6 ) 编写 app.controller.ts

import { Controller, Get } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from './user/user.entity';
import { InjectModel } from '@nestjs/mongoose';
import { Model, Document as Doc } from 'mongoose';

@Controller()
export class AppController {
  constructor(
    @InjectModel('User') private userModel: Model<Doc>,
  ) {}

  @Get('/multi-mongoose')
  async getMongooseUsers(): Promise<any> {
    const rs = await this.userModel.find()
    return rs;
  }
}

测试


1 ) 测试1

  • 请求

    curl --request GET \
      --url http://localhost:3000/multi-mongoose \
      --header 'x-tenant-id: 1'
    
  • 响应

    [
      {
        "_id": "6874d4b0d10e36e350dd588d",
        "username": "mongo1",
        "password": "123456"
      },
      {
        "_id": "6874d4d9d10e36e350dd588f",
        "username": "lee",
        "password": "123456"
      }
    ]
    

2 ) 测试2

  • 请求

    curl --request GET \
      --url http://localhost:3000/multi-mongoose \
      --header 'x-tenant-id: 2'
    
  • 响应

    [
      {
        "_id": "6874d4b0d10e36e350dd588d",
        "username": "mongo2",
        "password": "123456"
      },
      {
        "_id": "6874d4d9d10e36e350dd588f",
        "username": "lee",
        "password": "123456"
      }
    ]
    

3 )进入其中一个数据库,测试连接情况

docker exec -it mongo_app2 mongosh admin -u root # 输入密码
use nestmongodb;

# 其实上面一个 use 命令 可以不用
db.serverStatus().connections

输出如下:

{
  current: 6,
  available: 838839,
  totalCreated: 38,
  rejected: 0,
  active: 2,
  threaded: 6,
  exhaustIsMaster: Long('0'),
  exhaustHello: Long('0'),
  awaitingTopologyChanges: Long('1'),
  loadBalanced: Long('0')
}

目前 active 有2个,当不断请求同一个接口,在此执行上述命令,可发现这个数字是累加的
这明显是不对的,访问相同链接应该使用同一个 connection, 而不是创建新的通道
当租户多起来的时候,会带来严重的性能问题

4 ) 分析原因

在 @nestjs/mongoose 包的 mongoose-core.module.ts 中 在 使用 factory 方法的时候,会调用 createMongooseConnection

private static async createMongooseConnection(
   uri: string,
   mongooseOptions: ConnectOptions,
   factoryOptions: {
     lazyConnection?: boolean;
     onConnectionCreate?: MongooseModuleOptions['onConnectionCreate'];
   },
): Promise<Connection> {
   const connection = mongoose.createConnection(uri, mongooseOptions);

   if (factoryOptions?.lazyConnection) {
     return connection;
   }

   factoryOptions?.onConnectionCreate?.(connection);

   return connection.asPromise();
}

这个是每次都会创建的根本原因,这个包也没有提供 类似 datasourceFactory 的功能
现在需要定制 mongoose 的 module 中的 forRootSync 的逻辑

定制 mongoose 的 forRootAsync 方法

现在需要对官方 @nestjs/mongoose 包中的核心模块的方法进行裁剪和优化
找到对应的文件贴到自己项目中进行修改

1 )新建 src/database/mongoose/mongoose.module.ts

import { Module, DynamicModule } from '@nestjs/common';
import { 
	MongooseModuleAsyncOptions, 
	MongooseModuleOptions, 
	MongooseModule as NestMongooseModule
} from '@nestjs/mongoose';
import { MongooseCoreModule } from './mongoose-core.module';

@Module({})
export class MongooseModule extends NestMongooseModule {
	static forRoot(
		uri: string,
		options: MongooseModuleOptions = {},
	): DynamicModule {
		return {
			module: MongooseModule,
			imports: [MongooseCoreModule.forRoot(uri, options)],
		}
	}

	static forRootAsync(options: MongooseModuleAsyncOptions): DynamicModule {
		return {
			module: MongooseModule,
			imports: [MongooseCoreModule.forRootAsync(options)],
		}
	}
}

这是最外层,用于引入 MongooseCoreModule 模块, 重写有问题的源码

2 )新建 src/database/mongoose/mongoose-core.module.ts

import {
  DynamicModule,
  Global,
  Inject,
  Module,
  OnApplicationShutdown,
  Provider,
  Type,
} from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import * as mongoose from 'mongoose';
import { ConnectOptions, Connection } from 'mongoose';
import { defer, lastValueFrom } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { handleRetry } from './mongoose.utils';
import {
  MONGOOSE_CONNECTION_NAME,
  MONGOOSE_MODULE_OPTIONS,
} from './mongoose.constants';

import {
  MongooseModuleOptions,
  MongooseModuleAsyncOptions,
  MongooseModuleFactoryOptions,
  MongooseOptionsFactory,
  getConnectionToken,
} from '@nestjs/mongoose';

@Global()
@Module({})
export class MongooseCoreModule implements OnApplicationShutdown {
  private static connections: Record<string, mongoose.Connection> = {};
  constructor(
    @Inject(MONGOOSE_CONNECTION_NAME) private readonly connectionName: string,
    private readonly moduleRef: ModuleRef,
  ) {}

  static forRoot(
    uri: string,
    options: MongooseModuleOptions = {},
  ): DynamicModule {
    const {
      retryAttempts,
      retryDelay,
      connectionName,
      connectionFactory,
      connectionErrorFactory,
      lazyConnection,
      onConnectionCreate,
      verboseRetryLog,
      ...mongooseOptions
    } = options;

    const mongooseConnectionFactory =
      connectionFactory || ((connection) => connection);

    const mongooseConnectionError =
      connectionErrorFactory || ((error) => error);

    const mongooseConnectionName = getConnectionToken(connectionName);

    const mongooseConnectionNameProvider = {
      provide: MONGOOSE_CONNECTION_NAME,
      useValue: mongooseConnectionName,
    };

    const connectionProvider = {
      provide: mongooseConnectionName,
      useFactory: async (): Promise<any> =>
        await lastValueFrom(
          defer(async () =>
            mongooseConnectionFactory(
              await this.createMongooseConnection(uri, mongooseOptions, {
                lazyConnection,
                onConnectionCreate,
              }),
              mongooseConnectionName,
            ),
          ).pipe(
            handleRetry(retryAttempts, retryDelay, verboseRetryLog),
            catchError((error) => {
              throw mongooseConnectionError(error);
            }),
          ),
        ),
    };
    return {
      module: MongooseCoreModule,
      providers: [connectionProvider, mongooseConnectionNameProvider],
      exports: [connectionProvider],
    };
  }

  static forRootAsync(options: MongooseModuleAsyncOptions): DynamicModule {
    const mongooseConnectionName = getConnectionToken(options.connectionName);

    const mongooseConnectionNameProvider = {
      provide: MONGOOSE_CONNECTION_NAME,
      useValue: mongooseConnectionName,
    };

    const connectionProvider = {
      provide: mongooseConnectionName,
      useFactory: async (
        mongooseModuleOptions: MongooseModuleFactoryOptions,
      ): Promise<any> => {
        const {
          retryAttempts,
          retryDelay,
          uri,
          connectionFactory,
          connectionErrorFactory,
          lazyConnection,
          onConnectionCreate,
          verboseRetryLog,
          ...mongooseOptions
        } = mongooseModuleOptions;

        const mongooseConnectionFactory =
          connectionFactory || ((connection) => connection);

        const mongooseConnectionError =
          connectionErrorFactory || ((error) => error);

        return await lastValueFrom(
          defer(async () =>
            mongooseConnectionFactory(
              await this.createMongooseConnection(
                uri as string,
                mongooseOptions,
                { lazyConnection, onConnectionCreate },
              ),
              mongooseConnectionName,
            ),
          ).pipe(
            handleRetry(retryAttempts, retryDelay, verboseRetryLog),
            catchError((error) => {
              throw mongooseConnectionError(error);
            }),
          ),
        );
      },
      inject: [MONGOOSE_MODULE_OPTIONS],
    };
    const asyncProviders = this.createAsyncProviders(options);
    return {
      module: MongooseCoreModule,
      imports: options.imports,
      providers: [
        ...asyncProviders,
        connectionProvider,
        mongooseConnectionNameProvider,
      ],
      exports: [connectionProvider],
    };
  }

  private static createAsyncProviders(
    options: MongooseModuleAsyncOptions,
  ): Provider[] {
    if (options.useExisting || options.useFactory) {
      return [this.createAsyncOptionsProvider(options)];
    }
    const useClass = options.useClass as Type<MongooseOptionsFactory>;
    return [
      this.createAsyncOptionsProvider(options),
      {
        provide: useClass,
        useClass,
      },
    ];
  }

  private static createAsyncOptionsProvider(
    options: MongooseModuleAsyncOptions,
  ): Provider {
    if (options.useFactory) {
      return {
        provide: MONGOOSE_MODULE_OPTIONS,
        useFactory: options.useFactory,
        inject: options.inject || [],
      };
    }
    // `as Type<MongooseOptionsFactory>` is a workaround for microsoft/TypeScript#31603
    const inject = [
      (options.useClass || options.useExisting) as Type<MongooseOptionsFactory>,
    ];
    return {
      provide: MONGOOSE_MODULE_OPTIONS,
      useFactory: async (optionsFactory: MongooseOptionsFactory) =>
        await optionsFactory.createMongooseOptions(),
      inject,
    };
  }

  private static async createMongooseConnection(
    uri: string,
    mongooseOptions: ConnectOptions,
    factoryOptions: {
      lazyConnection?: boolean;
      onConnectionCreate?: MongooseModuleOptions['onConnectionCreate'];
    },
  ): Promise<Connection> {
    // 添加这里
    if (this.connections[uri]) {
      return this.connections[uri];
    }

    const connection = mongoose.createConnection(uri, mongooseOptions);
    this.connections[uri] = connection; // 这里赋值


    if (factoryOptions?.lazyConnection) {
      return connection;
    }

    factoryOptions?.onConnectionCreate?.(connection);

    return connection.asPromise();
  }

  async onApplicationShutdown() {
    const connection = this.moduleRef.get<any>(this.connectionName);
    if (connection) {
      await connection.close();
    }
    const connectionClients = Object.values(MongooseCoreModule.connections);
    if (connectionClients.length > 0) {
      // 销毁所有 mongoose connection
      for (const client of connectionClients) {
        client?.close();
      }
    }
  }
}

这里,注意 createMongooseConnection 以及 onApplicationShutdown 中的 处理
对连接进行优化处理,以及在异常关闭时对客户端进行销毁

3 )src/database/mongoose/mongoose.constants.ts

// 这个配置模拟调接口/读数据库获取的
export const tenantMap = new Map([
	['1', 'T1'],
	['2', 'T2']
]);
export const defaultTenant = tenantMap.values().next().value; // 第一个

// 新增如下
export const DEFAULT_DB_CONNECTION = 'DatabaseConnection';
export const MONGOOSE_MODULE_OPTIONS = 'MongooseModuleOptions';
export const MONGOOSE_CONNECTION_NAME = 'MongooseConnectionName';

export const RAW_OBJECT_DEFINITION = 'RAW_OBJECT_DEFINITION';

3 )src/database/mongoose/mongoose.utils.ts

import { Logger } from '@nestjs/common';
import { Observable } from 'rxjs';
import { delay, retryWhen, scan } from 'rxjs/operators';

export function handleRetry(
  retryAttempts = 9,
  retryDelay = 3000,
  verboseRetryLog = false,
): <T>(source: Observable<T>) => Observable<T> {
  const logger = new Logger('MongooseModule');
  return <T>(source: Observable<T>) =>
    source.pipe(
      retryWhen((e) =>
        e.pipe(
          scan((errorCount, error) => {
            const verboseMessage = verboseRetryLog
              ? ` Message: ${error.message}.`
              : '';
            const retryMessage =
              retryAttempts > 0 ? ` Retrying (${errorCount + 1})...` : '';

            logger.error(
              [
                'Unable to connect to the database.',
                verboseMessage,
                retryMessage,
              ].join(''),
              error.stack,
            );
            if (errorCount + 1 >= retryAttempts) {
              throw error;
            }
            return errorCount + 1;
          }, 0),
          delay(retryDelay),
        ),
      ),
    );
}
  • 工具包的部分函数

4 )测试

  • 重启项目,连入其中一个mongo 的 docker 容器,进入数据库,执行 db.serverStatus().connections
  • 调用对应数据库的租户标识的接口,再次执行 db.serverStatus().connections
  • 可看到 current 和 active 增加了,后面多次调用同一租户接口则不会再增加
  • 结束程序,则会销毁客户端,相应数字会同步减少
  • 这样就完成了相关功能

总结

  • 问题本质:Mongoose 在 NestJS 中的连接未复用,导致连接数异常增长
  • 核心影响:IO 资源浪费、性能下降、数据库连接池耗尽
  • 解决思路:
    • 在服务层缓存已有连接
    • 修改 Mongoose 模块源码逻辑
    • 使用第三方连接池库进行封装
    • 最佳实践:
      • 多租户系统中应确保数据库连接的唯一性与复用性
      • 建议对 Mongoose 模块进行轻量级封装以适配业务需求
SaaS核心架构实战:BFF、Schema隔离与自建Auth/Billing设计 SaaS(软件即服务)作为一种多租户、按需交付的云原生应用范式,其架构本质区别于传统单体系统——关键在于租户隔离、弹性计费与身份治理的深度耦合。原理上,必须通过数据库Schema级隔离保障数据安全边界,借助BFF(Backend for Frontend)层实现租户策略动态注入,而非简单API聚合;技术价值体现在故障域收敛、合规可控与商业灵活性,支撑企业客户对AD集成、自定义计费模型、品牌化登录等高阶需求。典型应用场景包括CRM、供应链协同、在线教育等垂直SaaS平台的从0到1落地与规模化演进。本文聚焦真实 阅读详情

相关推荐

Nestjs框架: 基于TypeORM的多租户功能集成和优化

本文介绍了如何通过Docker Compose配置多租户数据库环境(包含2个MySQL和1个PostgreSQL实例),并集成到NestJS项目中。首先展示了docker-compose配置文件,详细说明了三个数据库服务的端口映射、数据卷挂载等关键配置。接着通过Adminer工具验证数据库连接并创建测试库。然后重点讲解了TypeORM CLI的配置方法,包括环境变量设置、ORM配置文件编写以及数据库同步脚本的创建。最后提供了针对不同数据库的.env配置示例,并说明了如何执行TypeORM同步命令来初始化数据

Wang的专栏 1717

nestjs-mtenant:为多租户架构提供深度集成支持

nestjs-mtenant:为多租户架构提供深度集成支持 项目介绍 nestjs-mtenant 是一个为 NestJS 框架设计的模块,旨在提供多租户(multitenancy)支持,并实现与系统的深度集成。多租户架构是现代软件开发中广泛采用的一种部署模式,它允许在同一应用程序中为多个客户或组织(租户)提供服务。在这种模式下,各个租户的数据通常是隔离的,但有时为了业务需求,需要在租户之间共享数...

gitblog_01015的博客 758

mongodb_multi_tenancy:使用 Nodejs 和 mongodb 的多租户示例

使用MongoDB和Nodejs演示多租户应用程序 **如何运行? git clone https://github.com/Aijaz12550/mongodb_multi_tenancy.git yarn yarn start ** 如果您可以随意协作并使此示例更有用。 ** 谢谢 **

Nestjs框架: 多租户与多数据库的架构设计与实现——TaoToken统一Key接入下的配置骨架

NestJS 多租户多数据库架构的核心难点在于请求级数据源路由。本文给出可复用配置骨架:用 x-tenant-id 请求头驱动 TypeORM、Prisma、Mongoose 三类 ORM 共存,配合 settings.json 与 config.toml 让 AI 编码工具稳定读写。通过 TaoToken 统一 Key 完成一次真实请求验证,官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 可注册接入。

weixin_42168902的博客 153

nestjs-tenancy:nestjs 的多租户方法 - 目前仅支持 mongodb with mongoose

nestjs-租户 一个简单易用的 NestJs 和 Mongoose 多租户模块 描述 用于多租户模块。 安装 $ npm i --save @needle-innovision/nestjs-tenancy 基本用法 app.module.ts import { Module } from "@nestjs/common" ; import { TenancyModule } from "@needle-innovision/nestjs-tenancy" ; import { CatsModule } from "./cat.module.ts" ; @ Module ( { imports : [ TenancyModule . forRoot ( { tenantIdentifier : 'X-TENANT-ID' , option

multi-tenant-nestjs

一个渐进式的框架,用于构建高效且可扩展的服务器端应用程序。 描述 框架TypeScript入门资料库。 安装 $ npm install 运行应用 # development $ npm run start # watch mode $ npm run start:dev # production mode $ npm run start:prod 测试 # unit tests $ npm run test # e2e tests $ npm run test:e2e # test coverage $ npm run test:cov 支持 Nest是MIT许可的开源项目。 得益于赞助商和出色支持者的支持,它可以发展壮大。 如果您想加入他们,请。 保持联系 作者 网站-https: 执照 Nest已。

HeyForm:开源表单构建器的架构设计与核心技术解析

HeyForm是一款现代化的开源表单构建器,专为中小型企业打造,提供从表单设计、数据收集到分析管理的完整解决方案。本文将从技术架构、核心机制和实现原理三个维度,深入解析这个项目如何通过创新的设计实现高效、灵活的表单处理能力。 ## 本文亮点 - **模块化架构设计**:基于Monorepo的模块化架构,实现前后端分离与代码复用 - **智能表单逻辑引擎**:支持条件逻辑、变量计算和动态字段展示

gitblog_00252的博客 463

graphql-transform-federation:将现有的GraphQL模式转换为联合模式

graphql-transform-federation 如果要使用,但是无法重建当前的GraphQL架构,则可以使用此转换将GraphQL联合身份验证功能添加到现有模式。 当您使用托管的GraphQL服务或不支持联合(尚未)的生成的架构时,需要使用它。 如果使用的是apollo服务器或其他支持联合身份验证的架构构建器,则不需要此转换,则应直接 。 此转换将添加解析器和指令以符合。 可以重用许多,以确保它符合规范。 请查看的以获取更多背景信息。 用法 您可以在本地或远程GraphQL模式上使用此转换。 使用远程模式时,您的服务将充当中间件层,如上图所示。 请查看,以获取如何获取可用于此

vue3+nodejs基于微服务架构的校内电动车租赁系统的设计与实现

Vue 采用组件化开发模式,开发者可以将页面拆分成一个个独立的组件,每个组件都有自己的 HTML、CSS 和 JavaScript 代码,实现了高度的复用性和可维护性。MySQL 是关系型数据库管理系统的代表, 因为MySQL是其免费开源的,而且MySQL的功能已经足够用对于学习和中小型企业来讲,所以开发中小型网站都会选择MySQL作为网站的数据库。毕设项目前端使用vue框架,后端使用js的node,满足用户的讯息接受,信息搜索,资讯查看的操作。需要成品或者定制,加我们的时候,不满意的可以定制。

wx_bishe288的博客 1308

全栈Next.js应用平台:一个支持多租户和自定义域的全栈Next.js应用程序的开发平台

一个支持多租户和自定义域的全栈Next.js应用程序的开发平台。它基于Next.js应用路由器和Vercel域API,提供了强大的功能和灵活性,让开发人员可以构建具有多租户和自定义域的全栈应用程序。

手机拍照对焦快准狠的秘密:一文看懂PDAF、Dual Pixel和Super PD的区别

本文深入解析手机拍照中的三种主流相位检测自动对焦(PDAF)技术:Shield Pixel、Super PD和Dual Pixel。通过对比它们的原理、优缺点及实际表现,帮助读者理解不同技术的对焦速度、暗光表现和画质影响,从而做出更明智的购机选择。

weixin_26985923的博客 490

ToolJet开源低代码平台:企业级应用开发的技术架构与实践指南

ToolJet作为开源低代码平台的典范,为企业级应用开发提供了完整的技术解决方案。通过可视化拖放界面和丰富的组件库,开发团队能够快速构建复杂的企业应用、数据仪表板和业务流程自动化工具,显著提升开发效率并降低技术门槛。本指南深入解析ToolJet的技术架构、部署方案和最佳实践,为技术决策者和开发者提供全面的实施参考。 ## 技术挑战与解决方案概述 传统企业应用开发面临开发周期长、技术栈复杂、维护

gitblog_01029的博客 425

AG32VF407直流电流采集仪设计:从1uA到10A宽范围采样与FPGA加速方案

本文详细介绍了基于AG32VF407芯片的直流电流采集仪设计方案,实现了从1uA到10A的宽范围采样。方案采用低噪声电源、精密运放调理、高速比较器自动档位切换及高精度ADC同步采样,并重点阐述了利用AG32VF407片内FPGA实现硬件加速以提升性能的架构思路,为精密电流测量提供了完整的硬件与FPGA加速解决方案。

weixin_33245447的博客 312

开发和测试主流技术栈速查指南

本文采用“主流/常见/适合”这类稳健表达,不写死容易过期的具体版本号。技术选型应结合团队经验、业务规模、合规要求、预算、现有系统和维护成本,不建议只按流行度决策。测试技术栈不应只覆盖 UI 自动化,应同时覆盖单元测试、接口测试、集成测试、契约测试、性能测试、安全测试、数据验证和可观测性。AI 编程助手适合提升效率,但生成代码、测试用例、SQL、正则、配置和安全策略必须经过人工复核。高风险场景包括认证授权、支付、权限、数据删除、数据库迁移、批量任务、消息补偿、生产配置和回滚策略。

KeepJoy的博客 440

演化计算(蚁群算法、粒子群算法、遗传算法、演化规则......)

演化算法1.概念2.解决的问题 1.概念 传统算法(如梯度下降法、爬山法)根据导数的性质决定迭代的方向;而演化计算是一组解在一起协同作用产生一个最优解。 2.解决的问题 预测优化:演化计算(蚁群算法、粒子群算法、遗传算法等) 解的聚散、多样性:通过多次聚散,尽快得出最优解(某个解不合要求,要能够发散出去) ...

dear_jing的博客 4165

双向晶闸管交流调压基础知识及Multisim电路仿真

本文介绍了双向晶闸管交流调压电路的工作原理及应用。该电路由双向晶闸管、RC移相电路和双向触发二极管组成,通过调节电位器改变触发角实现电压调节。当电位器阻值减小时,触发角减小,输出电压升高;阻值增大时,触发角增大,输出电压降低。Multisim仿真展示了不同电位器档位下的波形特征:0%档位时全导通,50%档位时半导通,100%档位时基本关断。该电路结构简单、调节平滑,广泛应用于调光、调速等场合,但会产生谐波干扰。

XU157303764的博客 844

推荐文章:探索Nest.js的高效之道 —— 使用@nestjs/passport构建强大认证系统

在当今快速发展的Web开发领域,选择一个既强大又灵活的框架至关重要。今天,我们聚焦于【Nest.js】—— 一个快速崛起的Node.js服务器端框架,它不仅为开发者提供了构建高效、可扩展应用的强大工具箱,还带来了开发上的极大乐趣。通过深入探讨【@nestjs/passport】模块,我们将揭示如何利用这一利器打造坚如磐石的应用认证系统。 ## 项目介绍 **Nest.js**是一个面向未来的渐

gitblog_00394的博客 870

Databricks Genie:语义编译器架构与企业级智能解析实践

语义层是现代数据平台实现自然语言查询的核心基础,其本质是将业务术语映射为可执行、可治理的数据契约。原理上依赖结构化元数据、值字典与数据血缘三支柱协同,通过分层AI代理(如Intent Parsing、Retriever、Verifier)实现意图识别→语义对齐→安全执行的闭环。技术价值在于降低SQL使用门槛的同时保障审计性、一致性和权限可控性,适用于销售运营、财务分析等一线业务场景。本文深入解析Databricks Genie作为典型语义编译器的落地逻辑,涵盖Unity Catalog知识注入、七层Agen

weixin_29053695的博客 338

NestJS多租户模块最佳实践

NestJS多租户模块最佳实践 本文档将详细介绍如何在NestJS项目中集成和使用nestjs-tenancy模块,以实现多租户架构。我们将涵盖项目介绍、快速启动、应用案例和最佳实践以及典型生态项目。 项目介绍 nestjs-tenancy是一个简单易用的多租户模块,用于NestJS和Mongoose。它允许您为每个租户提供独立的数据库实例,从而实现数据隔离和安全性。该模块目前仅支持Mongodb...

gitblog_00928的博客 523

CKA-CN考试之路----15

这题和前面的那题类似.他让kube-scheduler必须满足4个CPU,我们刚才也看了,单个节点只有2个CPU。编辑/etc/kubernetes/manifests/kube-scheduler.yaml看下。根据报错信息来看 https://11.0.1.111:6443/api?查看etcd状态,是正常的.并且对本机的2379和11.0.1.111的2379监听。这里我们改成了剩余可用的10%,保存退出后可以看到。第一次退出root用户,第二次退回到初始节点。

Q先生 214

基于HetConv与Dual策略的YOLOv8轻量化设计-CSPHet结构的优化与实现

在计算机视觉中,YOLOv8作为目标检测领域的最新版本,通过引入多种改进来提升检测精度和速度。然而,提升精度和保持实时性能往往需要更多参数与计算量。本文提出一种结合Dual思想与HetConv的新型轻量化结构,称为CSPHet。CSPHet能够在保持YOLOv8检测精度的基础上有效减少约70万参数量,实现模型的轻量化和速度的提升。HetConv(Heterogeneous Convolution)是一种混合卷积技术,通过同时使用1x1、3x3等不同大小的卷积核,来捕捉不同尺度的特征。

博客 1328

NestJS 构建下一代 Node.js 应用

NestJS 构建下一代 Node.js 应用

weixin_42390185的博客 1293

深入YOLOv8内部核心:用RepNCSPELAN4深度重构主干网络,即插即用的高效涨点方案

本文提出了一种改进YOLOv8目标检测模型的方法,通过在YOLOv8中引入RepNCSPELAN模块来提高检测性能。该模块融合了RepVGG重参数化思想与CSP-ELAN结构,在保持推理速度的同时增强特征表达能力。实验结果表明,改进后的RepNCSPELAN-YOLOv8相比原模型在多项指标上均有提升:准确率从0.84提高到0.86,召回率从0.72提升至0.81,mAP50和mAP50-95也分别提高了0.06和0.04。文章详细介绍了模块设计原理和移植步骤,包括配置文件创建、代码修改等关键环节,为YOL

FJN110的博客 191

SpringBoot+MongoDB实现多租户切换

参考1:https://blog.youkuaiyun.com/daibang2182/article/details/80585004    参考2: https://github.com/Loki-Afro/multi-tenant-spring-mongodb   MongoDB多租户实现(数据库层面切换)   目标:实现不同分组的用户访问自己的专用数据库(数据库在同一个服务器).例:A公司用户,...

jei0439的博客 6157

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Nest入门教程 - 初识控制器

Nest入门教程 - 初识控制器 控制器 控制器负责处理传入的请求和向客户端返回响应。 控制器的目的是接收应用的特定请求。路由机制控制哪个控制器接收哪些请求。通常,每个控制器有多个路由,不同的路由可以执行不同的操作。 为了创建一个基本的控制器,我们必须使用装饰器。装饰器将类与所需的元数据关联,并使Nest能够创建路由映射(将请求绑定到相应的控制器)。...

YunWisdom 2155

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

多租户(multi-tenant)

当一个使用SaaS模式部署的软件同时有多个企业用户租用时,每一个企业都是独立的租用者,我们通常称他为:租户(tenant);同时有多个租用者,那就是多租户(multi-tenant)。多租户(Multi-tenant)是SaaS最重要的核心概念和关键技术。 ...

Leon_Jinhai_Sun的博客 1575
上一篇: Nestjs框架: 基于Prisma的多租户功能集成和优化
下一篇: Nestjs框架: 多租户与多数据库的架构设计与实现
Wang's Blog
博客等级 码龄12年 7438粉丝 2422原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值