概述

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

开始集成


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

services:mongo:image: mongo:8  # 使用最新的 MongoDB 镜像container_name: mongo_apprestart: alwaysports:- "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.logvolumes:- ./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/logsnetworks:- light_networkmongo-express:image: mongo-express:latestcontainer_name: mongo_express_apprestart: alwaysenvironment:ME_CONFIG_MONGODB_ADMINUSERNAME: rootME_CONFIG_MONGODB_ADMINPASSWORD: 123456_mongodbME_CONFIG_MONGODB_URL: mongodb://root:123456_mongodb@mongo:27017/ME_CONFIG_BASICAUTH: falseports:- 18081:8081networks:- light_networkmongo2:image: mongo:8  # 使用最新的 MongoDB 镜像container_name: mongo_app2restart: alwaysports:- "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.logvolumes:- ./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/logsnetworks:- light_networkmongo-express2:image: mongo-express:latestcontainer_name: mongo_express_app2restart: alwaysenvironment:ME_CONFIG_MONGODB_ADMINUSERNAME: rootME_CONFIG_MONGODB_ADMINPASSWORD: 123456_mongodbME_CONFIG_MONGODB_URL: mongodb://root:123456_mongodb@mongo2:27017/ME_CONFIG_BASICAUTH: falseports:- 18082:8081networks:- light_networknetworks: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#31603const 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 connectionfor (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
  • 可看到 currentactive 增加了,后面多次调用同一租户接口则不会再增加
  • 结束程序,则会销毁客户端,相应数字会同步减少
  • 这样就完成了相关功能

总结

  • 问题本质:Mongoose 在 NestJS 中的连接未复用,导致连接数异常增长
  • 核心影响:IO 资源浪费、性能下降、数据库连接池耗尽
  • 解决思路:
    • 在服务层缓存已有连接
    • 修改 Mongoose 模块源码逻辑
    • 使用第三方连接池库进行封装
    • 最佳实践:
      • 多租户系统中应确保数据库连接的唯一性与复用性
      • 建议对 Mongoose 模块进行轻量级封装以适配业务需求

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/93277.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/93277.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/93277.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何利用机器学习分析筛选生物标记物

在生物信息学中&#xff0c;Lasso回归、随机森林&#xff08;Random Forest&#xff09;和XGBoost因其各自的特性和优势&#xff0c;被广泛应用于基因组学、蛋白质组学、药物发现和疾病机制研究等领域。 Lasso回归 癌症亚型分类&#xff1a;从TCGA数据中筛选驱动基因&#xf…

计算机网络(基础篇)

TCP/IP 网络模型 应用层&#xff08;Application Layer&#xff09; 应用层只需要专注于为用户提供应用功能&#xff0c;比如 HTTP、FTP、Telnet、DNS、SMTP等。应用层是工作在操作系统中的用户态&#xff0c;传输层及以下则工作在内核态。传输层&#xff08;Transport Layer&a…

全面解析 CSS Flex 布局:从入门到精通的所有属性详解

1. Flex 容器属性 通过 display: flex 或 display: inline-flex 将元素设置为 Flex 容器。以下是所有容器属性。 1.1 display: flex | inline-flex 作用&#xff1a;定义一个 Flex 容器。可选值&#xff1a; flex&#xff1a;块级容器&#xff0c;占据整行。inline-flex&#x…

数据结构:对角矩阵(Diagonal Matrix)

目录 矩阵的传统表示&#xff1a;二维数组 &#x1f50d; 真正有用的数据是哪些&#xff1f; 从二维数组转为一维数组 用 C 类实现对角矩阵 1. 对角矩阵真正需要存什么&#xff1f; 2. 对角矩阵允许哪些行为&#xff1f; 3. 为什么要动态分配数组&#xff1f; 接下来推…

Leetcode_349.两个数组的交集

这道题的意思很明确&#xff0c;就是让寻找两个数组中的共同元素&#xff0c;并去重&#xff0c;由此可以联想到哈希表的特性&#xff0c;注意到题目给的数据范围&#xff0c;在1000以内&#xff0c;所以本题可以使用 STL 的库函数&#xff0c;也可以使用数组进行模拟。 本题要…

STM32——寄存器映射

总 &#xff1a;STM32——HAL库总结-CSDN博客 芯片资料&#xff1a; STM32F1系列参考手册-V10&#xff08;中&#xff09; STM32F103ZET6(English) 一、寄存器基础 1.1 简介 单片机内部的控制机构。 像空气开关控制电路一样的原理&#xff0c;打开关闭某个开关&#xff0…

Java响应式编程

Java 响应式编程是一种基于异步数据流处理的编程范式&#xff0c;它强调数据流的声明式构建和传播变化的自动响应。Java 9 引入的Flow API为响应式编程提供了标准接口&#xff0c;而 Reactor 和 RxJava 等第三方库则提供了更丰富的操作符和工具。核心概念Publisher&#xff08;…

【重学数据结构】二叉搜索树 Binary Search Tree

目录 二叉搜索树的数据结构 手写实现二叉搜索树 树节点定义 插入节点 源码 流程图 二叉树插入步骤图解 第一步: 插入 20 第二步: 插入 10 第三步: 插入 30 第四步: 插入 5 查找节点 源码 场景一: 查找成功 (search for 25) 第一步: 从根节点开始 第二步:…

四、计算机组成原理——第1章:计算机系统概述

目录 1.1计算机发展历程 1.1.1计算机硬件的发展 1.计算机的四代变化 2.计算机元件的更新换代 1.1.2计算机软件的发展 1.2计算机系统层次结构 1.2.1计算机系统的组成 1.2.2计算机硬件 1.冯诺依曼机基本思想 2.计算机的功能部件 (1)输入设备 (2)输出设备 (3)存储器 (4)运算器 (5)…

flutter TextField 失去焦点事件

在 Flutter 中&#xff0c;处理 TextField 的失去焦点事件&#xff08;即失去焦点时触发的操作&#xff09;通常有两种常用方式&#xff1a;使用 FocusNode 或 onEditingComplete 回调。以下是具体实现&#xff1a; import package:flutter/material.dart;class MyTextField e…

Moonlight for ChromeOS 常见问题解决方案

Moonlight for ChromeOS 常见问题解决方案 项目基础介绍 Moonlight for ChromeOS 是一个开源的 NVIDIA GameStream 客户端&#xff0c;允许用户将他们的游戏从高性能的桌面电脑流式传输到运行 ChromeOS 的设备上。该项目还支持 Android 和 iOS/tvOS 平台。Moonlight for Chrome…

SQL语句:读操作、写操作、视图

文章目录读操作分类基础查询语句示例高级查询--分组查询、子查询、表连接、联合查询分组查询&#xff1a;子查询&#xff08;嵌套查询&#xff09;表连接联合查询写操作视图SQL&#xff1a;结构化查询语言读操作 重点是where查询&#xff0c;即高级查询部分 分类 DML &#…

Python 机器学习实战:基于 Scikit-learn

本文围绕《Python 机器学习实战&#xff1a;基于 Scikit-learn 的项目开发》展开&#xff0c;先介绍 Scikit-learn 库的基础特性与优势&#xff0c;再阐述机器学习项目开发的完整流程&#xff0c;包括数据收集与预处理、模型选择与训练、评估与优化等。通过具体实战案例&#x…

java里List链式编程

java里对list的操作&#xff0c;我们一遍使用for遍历&#xff0c;输出或改变里面的内容。单经常在代码里面我们发现&#xff0c;也可以使用这样的代码结构daPaymentActionVo.setApnolist(paymentActionVo.getApnolist().stream().map(PaymentActionVo.Voucher::getApno).collec…

【esp32s3】7 - VSCode + PlatformIO + Arduino + 构建项目

一、PlatformIO 1.1. 概述 官方文档&#xff1a;What is PlatformIO? PlatformIO 是一个跨平台的物联网开发生态系统&#xff0c;专门为嵌入式系统开发设计&#xff0c;支持多种开发板和框架。 1.1.1. 主要特点 跨平台&#xff1a;支持 Windows、macOS 和 Linux多框架支持&…

LE AUDIO CIS/BIS音频传输时延的计算

LE AUDIO音频总时延计算方法 按照BAP的规范,LE AUDIO音频总延时包括三个部分:Audio Processing Time,Transport Latency,Presentation Delay。如下图所示是播放音乐的示例图: 这里还有一个麦克风录音的总时延示例图: Audio Processing Time:这个就是音频DSP获取音频数…

git 修改 更新

git 修改 更新先更新&#xff0c;后修改# 暂存当前修改 git add . git stash# 获取最新的 main 分支 git checkout main git pull# 新建开发分支 git checkout -b lbg_0727# ⚠️ 先把 main 的最新代码合并/变基到当前分支&#xff08;用于消除冲突&#xff09; # 方法1&#x…

飞鹤困局:增长神话的裂痕

增长天花板已然逼近&#xff0c;飞鹤需要探寻新方向。作者|安德鲁编辑|文昌龙“飞鹤&#xff0c;更适合中国宝宝体质”——这句曾让无数妈妈点头的广告语&#xff0c;帮飞鹤坐上了中国奶粉市场的头把交椅。可多年后&#xff0c;时代红利退潮&#xff0c;故事不好讲了。飞鹤的利…

Java设计模式之<建造者模式>

目录 1、建造者模式 2、建造者模式结构 3、实现 4、工厂模式对比 5、适用场景差异 前言 建造者模式是一种创建型设计模式。用于封装复杂对象的构建过程&#xff0c;通过步骤构建产品类。它包括产品类、抽象建造者、具体建造者和指挥者角色。 优点在于灵活性、解耦和易扩展…

fchown/fchownat系统调用及示例

55. fchmod - 通过文件描述符改变文件权限 函数介绍 fchmod是一个Linux系统调用&#xff0c;用于通过文件描述符来改变文件的访问权限。它是chmod函数的文件描述符版本&#xff0c;避免了路径名解析。 函数原型 #include <sys/stat.h> #include <unistd.h>int fchm…