You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 14:15:04