NocoBase CacheManager 缓存管理器源码级解析:从实例创建到 Store 注册
2026/9/13 11:00:13 网站建设 项目流程

NocoBase CacheManager 缓存管理器源码级解析:从实例创建到 Store 注册

【免费下载链接】nocobaseNocoBase is an open-source AI + no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase

导读

CacheManager是 NocoBase 的核心缓存管理模块,基于node-cache-manager生态封装,负责统一创建、注册与回收各类缓存。本文以 CacheManager 官方 API 文档 为主线,结合 packages/core/cache/src/cache-manager.ts 的完整实现与测试用例,深入讲解CacheManagerOptions配置、registerStore扩展注册、createCache实例创建、getCache/flushAll/close生命周期管理,并说明 NocoBase 服务端如何将它接入应用启动流程。读完本文,你将能独立在 NocoBase 插件中注册 Redis/Memory 缓存、自定义 Store 类型,并理解缓存命名空间与 key 前缀的隔离机制。

概览:CacheManager 的职责与内置 Store

CacheManager为 NocoBase 提供统一的 Cache 模块管理能力,其底层依赖node-cache-managercaching(store, config)工厂方法。内置的 Cache 类型有两种:

  • memory:由node-cache-manager默认提供的 lru-cache;
  • redis:由cache-manager-redis-yetredisStore支持。

更多类型(如自研的中间件缓存)可以通过registerStore()API 扩展注册。从 packages/core/cache/src/index.ts 可以看出,@nocobase/cache包同时导出CacheManagerCache、布隆过滤器(bloom-filter)与计数器(counter)四组能力,本文聚焦前两者。

三个核心概念

文档中明确了三个容易混淆的概念,理解它们有助于看懂后续所有 API:

概念说明
Store定义一种缓存方式,包含创建缓存的工厂方法和其他相关配置。每种缓存方式都有唯一标识,在注册时提供。内置标识即memoryredis
Store 工厂方法node-cache-manager及相关扩展包提供,即caching()方法的第一个参数,如'memory'redisStore
CacheNocoBase 封装的类,提供使用缓存的方法。实际读写操作的是Cache实例,每个实例有唯一标识,可作为区分不同模块的命名空间

在源码中,这三层结构对应CacheManager.storeTypes(注册的 Store 类型)、CacheManager.stores(已创建的底层 store 实例)与CacheManager.caches(已创建的Cache实例),三个 Map 分别在 cache-manager.ts 中声明。

构造函数:合并默认配置并注册内置 Store

签名与类型

constructor(options?: CacheManagerOptions)
export type CacheManagerOptions = Partial<{ defaultStore: string; stores: { [storeType: string]: StoreOptions; }; prefix: string; // 源码中额外支持,见下文 }>; type StoreOptions = { store?: 'memory' | FactoryStore<Store, any>; close?: (store: Store) => Promise<void>; // global config [key: string]: any; };

注意:官方文档的类型定义未包含prefix,但实际源码 cache-manager.ts 中的CacheManagerOptions还支持全局prefix,会作为所有 Cache 实例 key 前缀的默认值,下文createCache一节会说明其合并规则。

参数详细说明

CacheManagerOptions

属性类型描述
defaultStorestring默认 Cache 类型的唯一标识,createCache省略store时生效
storesRecord<string, StoreOptions>注册 Cache 类型,key 为类型唯一标识,值为包含注册方法与全局配置的对象
prefixstring(源码支持)全局默认 key 前缀,可选

StoreOptions

属性类型描述
storememory|FactoryStore<Store, any>store 工厂方法,对应caching第一个参数
close(store: Store) => Promise<void>可选。Redis 等需要建立连接的中间件需提供关闭连接的回调,入参为工厂方法返回的对象
[key: string]any其他 store 全局配置,对应caching第二个参数

默认 options 与深合并行为

源码构造函数中的默认配置如下(与文档一致):

import { redisStore, RedisStore } from 'cache-manager-redis-yet'; const defaultOptions: CacheManagerOptions = { defaultStore: 'memory', stores: { memory: { store: 'memory', // 全局配置 max: 2000, }, redis: { store: redisStore, close: async (redis: RedisStore) => { if (!redis.client?.isOpen) { return; } await redis.client.quit(); }, }, }, };

与文档示例相比,源码 cache-manager.ts 在close中增加了redis.client?.isOpen守卫:仅当连接处于打开状态时才调用quit(),避免对已关闭的连接重复调用导致异常。

options与默认值通过deepmerge进行深合并(见 cache-manager.ts),因此默认配置已有的内容可以缺省。例如只覆盖 Redis 的连接地址:

const cacheManager = new CacheManager({ stores: { redis: { // redisStore 已在默认 options 中提供,只需覆盖配置 url: 'redis://localhost:6379', }, }, });

构造完成后,代码会遍历stores,将每个 store 拆分为store工厂方法与其余全局配置,依次调用registerStore()完成注册(cache-manager.ts)。这与测试 cache-manager.test.ts 中手动registerStore({ name: 'memory', store: 'memory' })后再创建缓存的行为完全一致。

registerStore:扩展注册自定义缓存方式

用法示例

import { redisStore, RedisStore } from 'cache-manager-redis-yet'; cacheManager.registerStore({ // store 唯一标识 name: 'redis', // 创建 store 的工厂方法 store: redisStore, // 关闭 store 连接 close: async (redis: RedisStore) => { await redis.client.quit(); }, // 全局配置 url: 'xxx', });

签名与实现

registerStore(options: { name: string } & StoreOptions)

源码实现非常简洁(cache-manager.ts):

registerStore(options: { name: string } & StoreOptions) { const { name, ...rest } = options; this.storeTypes.set(name, rest); }

它将name拆出作为 Map 的 key,其余内容(工厂方法、close 回调、全局配置)整体存入storeTypes。注册本身不会建立任何连接,真正的连接在createCache首次创建时才会建立——这是按需初始化的设计,避免应用启动时无谓地创建连接。

createCache:创建 Cache 实例

用法示例

await cacheManager.createCache({ name: 'default', // cache 唯一标识 store: 'memory', // store 唯一标识 prefix: 'mycache', // 自动给缓存 key 加上 'mycache:' 前缀,可选 // 其他 store 配置,会和 store 全局配置合并 max: 2000, });

签名

createCache(options: { name: string; prefix?: string; store?: string; [key: string]: any }): Promise<Cache>

options 详细说明

属性类型描述
namestringcache 唯一标识
storestringstore 唯一标识
prefixstring可选,缓存 key 前缀
[key: string]any其他 store 相关的自定义配置项

源码中的两条关键分支

阅读 cache-manager.ts 的实现,可以归纳出createCache的完整决策逻辑:

async createCache(options: { name: string; prefix?: string; store?: string; [key: string]: any }) { const { name, store = this.defaultStore, ...config } = options; let { prefix } = options; prefix = this.prefix ? (prefix ? `${this.prefix}:${prefix}` : this.prefix) : prefix; if (!lodash.isEmpty(config) || store === 'memory') { const newStore = await this.createStore({ name, storeType: store, ...config }); return this.newCache({ name, prefix, store: newStore }); } const s = this.stores.get(store); if (!s) { const defaultStore = await this.createStore({ name: store, storeType: store }); return this.newCache({ name, prefix, store: defaultStore }); } return this.newCache({ name, prefix, store: s.store }); }

可以提炼出以下几点对使用者至关重要的行为:

  1. store省略时使用defaultStore:此时缓存方式会跟随系统默认缓存方式改变而改变,例如应用从 memory 切换为 redis 后,省略store的缓存自动改用 redis,实现"配置即切换"。

  2. 共享默认缓存空间:没有自定义配置时(且 store 非 memory),会复用该 store 类型下已创建的共享实例,即文档所说"返回由全局配置创建、当前缓存方式共享的默认缓存空间"。因此推荐加上prefix避免不同模块之间的 key 冲突

    // 使用默认缓存,使用全局配置 await cacheManager.createCache({ name: 'default', prefix: 'mycache' });
  3. memory 特殊处理:由于内存缓存本身独立于进程,条件store === 'memory'会让每次调用都为该 Cache 实例单独创建底层 store,确保不同命名空间的 memory 缓存互不干扰。

  4. 自定义配置触发独立实例:只要传入了额外配置(如maxttl),即使 store 相同也会走createStore创建独立实例,并将自定义配置与全局配置合并后传给caching()。这对应文档中createCache示例里max: 2000的语义。

  5. 未注册的 store 类型会报错createStore内部若在storeTypes中找不到对应类型,会抛出Create cache failed, store type [...] is unavailable or not registered(cache-manager.ts),提示先注册。

prefix 的全局与局部合并

当构造 CacheManager 时传入了全局prefix,且createCache也传了prefix,源码会将两者拼接为全局prefix:局部prefix;只传一个时则使用该值。NocoBase 服务端正是利用这一点,在应用层面统一注入命名空间(见下文"服务端集成")。

返回的 Cache 实例

createCache返回的是@nocobase/cache封装后的Cache类(参考 Cache 文档),其内部持有底层node-cache-manager的 store,并自动为每个 key 套用prefix:key的命名空间(见 cache.ts)。Cache类提供的方法包括:

  • 基础读写:get()set(key, value, ttl)del()reset()wrap()
  • 批量操作:mset()mget()mdel()
  • 命名空间查询:keys(pattern)ttl(key)
  • NocoBase 扩展:wrapWithCondition()(按条件决定是否使用缓存)、setValueInObject()/getValueInObject()/delValueInObject()(操作对象型缓存的单个字段)。

getCache:按名称获取缓存

cacheManager.getCache('default');

签名:getCache(name: string): Cache

源码中该方法是同步的,直接从未创建的缓存 Map 中查询(cache-manager.ts):

getCache(name: string): Cache { const cache = this.caches.get(name); if (!cache) { throw new Error(`Get cache failed, ${name} is not found`); } return cache; }

因此必须createCachegetCache,否则会抛出Get cache failed, xxx is not found。在实际使用中,getCache常用于跨模块共享:某个模块负责创建并注册缓存,其他模块通过名称获取同一实例,从而以name为命名空间实现缓存隔离与复用。

flushAll 与 close:生命周期管理

flushAll:重置所有缓存

await cacheManager.flushAll();

源码实现遍历所有已创建的Cache实例并逐个调用reset(),通过Promise.all并行执行(cache-manager.ts):

async flushAll() { const promises = []; for (const cache of this.caches.values()) { promises.push(cache.reset()); } await Promise.all(promises); }

注意它只清空caches中已创建的缓存实例,不会关闭连接,适合在数据变更后整体刷新缓存内容的场景。

close:关闭所有缓存中间件连接

await cacheManager.close();

源码遍历stores中每个已创建 store 的close回调并执行(cache-manager.ts):

async close() { const promises = []; for (const s of this.stores.values()) { const { close, store } = s; close && promises.push(close(store.store)); } await Promise.all(promises); }

只有注册 Store 时提供了close回调的才会被调用(如 Redis 的redis.client.quit()),memory 缓存无需关闭。测试 cache-manager.test.ts 验证了该行为:注册时传入closemock,调用cacheManager.close()后断言其被调用。该方法是应用优雅退出时释放连接的关键入口。

服务端集成:CacheManager 在 NocoBase 应用中的接入方式

CacheManager 并不仅仅是独立工具类,它已深度接入 NocoBase 服务端应用生命周期。核心代码在 packages/core/server/src/cache/index.ts:

export const createCacheManager = async (app: Application, options: CacheManagerOptions) => { const cacheManager = new CacheManager(options); const defaultCache = await cacheManager.createCache({ name: app.name }); app.cache = defaultCache; app.context.cache = defaultCache; return cacheManager; };

而 packages/core/server/src/application.ts 中应用加载时创建 CacheManager 的方式如下:

async createCacheManager() { this._cacheManager = await createCacheManager(this, { prefix: this.name, // 以应用名作为全局 key 前缀 ...this.options.cacheManager, }); return this._cacheManager; }

从中可以提炼出以下事实:

  1. 应用名即命名空间:NocoBase 自动以app.name作为 CacheManager 的全局prefix,因此默认缓存的 key 天然带有应用级隔离;开发者还可以通过options.cacheManager(即CacheManagerOptions)覆盖defaultStorestores等配置。
  2. 默认缓存实例直挂应用:创建后名为app.name的默认缓存会被赋给app.cacheapp.context.cache,业务代码中可以直接通过ctx.cache读写默认缓存,无需自行管理 CacheManager。
  3. 生命周期联动:应用load()时会先close()旧 CacheManager 再重建(application.ts),应用销毁时也会调用cacheManager.close()释放连接(application.ts),并通过this.context.cacheManager = this._cacheManager暴露给请求上下文(application.ts)。

进阶:源码中的实验性扩展(BloomFilter 与 Counter)

在 cache-manager.ts 中,CacheManager还提供了两个标注@experimental的方法,展示了 Cache 基座之上的扩展能力:

  • createBloomFilter(options?):基于缓存创建布隆过滤器,memorystore 返回MemoryBloomFilterredisstore 返回RedisBloomFilter,其他 store 抛出BloomFilter store [...] is not supported。实现上会尝试getCache('bloom-filter')复用,不存在则自动创建。
  • createCounter(options, lockManager?):创建计数器,memory返回MemoryCounterredis返回RedisCounter,其他 store 则要求传入LockManager并返回LockCounter,否则报错。

这两个 API 说明 CacheManager 不只是简单的 key-value 管理器,而是 NocoBase 缓存能力(包括分布式锁、计数、去重等场景)的统一入口,相关实现位于 packages/core/cache/src/bloom-filter 与 packages/core/cache/src/counter。

总结

围绕 CacheManager 文档,本文梳理了 NocoBase 缓存管理器的完整使用链路:

  • 构造:通过CacheManagerOptions声明defaultStorestores,与内置默认配置深合并;
  • 注册registerStore()name + StoreOptions扩展任意缓存方式;
  • 创建createCache()依据store/自定义配置决定复用共享实例还是新建实例,并用prefix隔离 key;
  • 使用getCache()按名称获取Cache实例,执行get/set/del/wrap等读写;
  • 回收flushAll()并行重置所有缓存,close()关闭所有中间件连接。

配合 源码实现、测试用例 与 服务端接入代码,开发者既可以作为使用者快速接入 Redis,也可以作为扩展者注册自定义 Store,甚至基于createBloomFilter/createCounter构建更复杂的分布式能力。

【免费下载链接】nocobaseNocoBase is an open-source AI + no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询