演示

添加缓存

在根模块上用 @Cache 声明缓存(内存或从环境变量读取的 Redis),用 @CacheClient 注入,并可选用 @CacheRoute / @CacheFn 缓存 HTTP 路由或方法结果。复用其他插件的缓存,不要重复声明。

目标与何时使用

将缓存用于重复读取、会话/令牌存储与昂贵计算。在所有者插件的根模块上声明一次客户端,然后在同一进程内任意处注入——包括其他插件。

  • 内存 — 单进程,简单 TTL / checkperiod
  • Redis — 跨实例共享;host / port / password 来自环境变量
  • 跨插件 — 注入所有者的缓存,不要为同一客户端再声明 @Cache

1. 前置条件

需要根 @Module;若用 Redis,启动时须能从 PluginEnv / base .env 取得凭证。

  • 已有根 @Module({ name: metadata.name, … })
  • Redis — 启动时可用 REDIS_HOST / PORT / PASSWORD(或自选环境变量名)
  • 若消费其他插件的缓存,在 pluginDependencies 中列出所有者
  • @Cache 必须加在 @Module 类上——不能加在 service 或 controller

2. 在根模块上声明 @Cache

同一插件打开多个缓存时传入唯一 name。同一模块类上允许多个 @Cache。

  • type — "in-memory" 或 Redis 选项(host、port、password 等)
  • name — 可选客户端 id;声明多个 @Cache 时需要
  • 所有者是正在加载的插件 — 消费方用该插件的 metadata.name 寻址
TSXdeclare caches
1import { Cache, Module } from "@quan-erp/shared-backend-core"; 2import metadata from "../../module.metadata.json" with { type: "json" }; 3 4@Cache({ 5 name: "publisher", 6 type: "in-memory", 7 checkperiod: 1000, 8}) 9@Cache({ 10 name: "session", 11 type: "redis", 12 host: process.env.REDIS_HOST, 13 port: Number(process.env.REDIS_PORT ?? 6379), 14 password: process.env.REDIS_PASSWORD, 15}) 16@Module({ 17 name: metadata.name, 18 providers: [...], 19 controllers: [...], 20}) 21export class MyPluginModule {}

3. 用 @CacheClient 注入

在服务属性上注入 ICache。第一个参数是拥有该缓存的插件;第二个是可选客户端名。

  • metadata.name — 本插件自己的缓存(多个时加 name)
  • "other-plugin" — 其他插件创建的缓存
  • CacheManager.DEFAULT_PLUGIN — 适用时的核心 / 共享默认所有者
TSXinject
1import { 2 CacheClient, 3 Service, 4} from "@quan-erp/shared-backend-core"; 5import type { ICache } from "@quan-erp/shared-backend-core"; 6import metadata from "../../module.metadata.json" with { type: "json" }; 7 8@Service() 9export class SessionService { 10 @CacheClient(metadata.name, "session") 11 private session: ICache; 12 13 async getToken(userId: string) { 14 return this.session.get(`token:${userId}`); 15 } 16}

4. 路由与方法辅助

让辅助指向与拥有该 @Cache 相同的 plugin + name。写入会使缓存失效时使用 delete 辅助。

  • @CacheRoute / @DeleteCacheRoute — HTTP 响应缓存;key 可为字符串或 (req) => string
  • @CacheFn / @DeleteCacheFn — 方法结果缓存;key 为 (...args) => string
  • plugin + name 须匹配拥有该客户端的 @Cache
TSroute cache
1@CacheRoute({ 2 key: (req) => `orders:${req.query.status ?? "all"}`, 3 plugin: metadata.name, 4 name: "session", 5}) 6@Get("/orders") 7list() { /* … */ }
TSmethod cache
1@CacheFn({ 2 key: (id: number) => `order:${id}`, 3 plugin: metadata.name, 4 name: "session", 5}) 6async findOne(id: number) { /* … */ }

5. 跨插件复用

缓存按所有者插件(+ 可选 name)登记在进程级注册表中。消费方不要为同一客户端再声明 @Cache,而是注入所有者的缓存,并让辅助指向相同的 plugin + name。

  • 所有者 — 声明 @Cache 并先启动(pluginDependencies)
  • 消费方 — @CacheClient("owner-plugin", "cache-name")
  • 路由 / 方法辅助 — 相同的所有者 plugin + name
  • 不要在每个消费方重复声明相同的 @Cache
TSconsumer inject
1@Service() 2export class ReportService { 3 // "warehouse" plugin declared @Cache({ name: "replica", ... }) 4 @CacheClient("warehouse", "replica") 5 private replica: ICache; 6}

6. 验证

  • 插件启动无缓存 / Redis 连接错误
  • 注入的 ICache get / set / del 行为符合预期
  • 缓存路由在失效前返回相同载荷
  • @DeleteCacheRoute / @DeleteCacheFn 清除目标 key

常见错误

  • 把 @Cache 加在 service 或 controller 上而不是根 @Module
  • 声明多个 @Cache 时漏掉 name
  • 硬编码 Redis 凭证而不是用环境变量
  • 在消费方重复声明 @Cache 而不是用 @CacheClient
  • @CacheClient 与 @CacheRoute / @CacheFn 的 plugin + name 不一致
  • 忘记 pluginDependencies,导致所有者在消费方之后加载