目标与何时使用
将缓存用于重复读取、会话/令牌存储与昂贵计算。在所有者插件的根模块上声明一次客户端,然后在同一进程内任意处注入——包括其他插件。
- 内存 — 单进程,简单 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,导致所有者在消费方之后加载