如何在tRPC 10中实现支持自定义TTL的服务端缓存中间件?
实现tRPC 10服务端内存缓存中间件(支持自定义TTL)
1. 定义缓存配置类型
先扩展tRPC的元数据类型,让每个procedure可配置缓存开关与TTL:
import type { ProcedureMeta } from '@trpc/server'; // 缓存配置结构 type CacheConfig = { enabled: boolean; ttl: number; // 过期时间,单位:毫秒 }; // 扩展tRPC元数据,支持缓存配置 declare module '@trpc/server' { interface ProcedureMeta { cache?: CacheConfig; } }
2. 实现内存缓存存储
用Map实现轻量内存缓存,同时添加定时清理过期缓存的逻辑:
// 缓存存储:key为「procedure路径+参数哈希」,value包含数据和过期时间 const cacheStore = new Map<string, { data: unknown; expiresAt: number }>(); // 定时清理过期缓存(每分钟执行一次) setInterval(() => { const currentTime = Date.now(); for (const [key, item] of cacheStore.entries()) { if (item.expiresAt < currentTime) { cacheStore.delete(key); } } }, 60_000);
3. 编写缓存中间件
中间件会检查当前procedure的缓存配置,命中缓存则直接返回,未命中则执行原逻辑并缓存结果:
import { initTRPC, TRPCError } from '@trpc/server'; import { hash } from 'ohash'; // 用于生成参数的唯一哈希值 const t = initTRPC.create(); const cacheMiddleware = t.middleware(async ({ next, meta, path, input }) => { const cacheConfig = meta?.cache; // 未启用缓存,直接执行原逻辑 if (!cacheConfig?.enabled) return next(); const currentTime = Date.now(); // 生成唯一缓存key:路径+参数哈希 const cacheKey = `${path}:${hash(input)}`; const cachedData = cacheStore.get(cacheKey); // 缓存命中且未过期,返回缓存数据 if (cachedData && cachedData.expiresAt > currentTime) { return { type: 'data', data: cachedData.data }; } // 缓存未命中,执行原procedure const result = await next(); // 仅缓存成功的结果,不缓存错误 if (result.type === 'data') { cacheStore.set(cacheKey, { data: result.data, expiresAt: currentTime + cacheConfig.ttl, }); } return result; }); // 创建带缓存中间件的procedure模板 export const cachedProcedure = t.procedure.use(cacheMiddleware);
4. 在业务procedure中使用
通过meta配置缓存,不同procedure可设置不同TTL,也可选择不启用缓存:
import { z } from 'zod'; // 示例路由 export const userRouter = t.router({ // 启用缓存:TTL 5分钟 getById: cachedProcedure .meta({ cache: { enabled: true, ttl: 5 * 60 * 1000 } }) .input(z.number()) .query(async ({ input }) => { // 模拟数据库查询 const user = await db.user.findUnique({ where: { id: input } }); if (!user) throw new TRPCError({ code: 'NOT_FOUND' }); return user; }), // 不启用缓存的mutation操作 update: t.procedure .input(z.object({ id: z.number(), name: z.string() })) .mutation(async ({ input }) => { const updatedUser = await db.user.update({ where: { id: input.id }, data: { name: input.name }, }); // 手动删除对应缓存,保证数据一致性 const cacheKey = `user.getById:${hash(input.id)}`; cacheStore.delete(cacheKey); return updatedUser; }), });
注意事项
- 参数哈希:使用
ohash可稳定处理复杂对象哈希,避免因对象键顺序不同导致缓存key不一致;也可使用JSON.stringify但需注意局限性。 - 缓存一致性:当mutation修改数据后,建议手动删除关联缓存key,避免返回过期数据。
- 内存缓存局限:内存缓存会随服务重启清空,如需持久化可替换为Redis等外部缓存,只需修改
cacheStore的实现即可。 - 错误缓存:中间件仅缓存成功结果,避免缓存错误状态影响用户体验。
内容的提问来源于stack exchange,提问作者swimmer
相关产品推荐
相关产品推荐

