路由缓存

VextJS 提供声明式路由级响应缓存,通过路由选项的 cache 字段配置。缓存命中时跳过参数校验和 handler 执行,直接返回缓存的响应数据,显著提升热点接口性能。

基本用法

数字简写

最简配置,指定缓存秒数:

import { defineRoutes } from 'vextjs';

export default defineRoutes((app) => {
  // 缓存 60 秒
  app.get('/products', { cache: 60 }, async (req, res) => {
    const products = await db.getProducts();
    res.json(products);
  });
});

完整配置

app.get('/products', {
  cache: {
    ttl: 120,                              // 缓存 120 秒
    vary: ['accept-language'],             // 不同语言分别缓存
    tags: ['products'],                    // 标签(用于批量失效)
    condition: (req) => !req.query.refresh, // 条件缓存
    cacheControl: true,                    // 设置 Cache-Control 头(默认 true)
  },
}, async (req, res) => {
  res.json(await db.getProducts());
});

显式禁用

app.get('/realtime', { cache: false }, async (req, res) => {
  res.json({ timestamp: Date.now() });
});

配置选项

RouteOptions.cache

字段类型默认值说明
ttlnumber缓存有效期(秒),必须 > 0
key(req) => string自动生成自定义缓存 key 生成函数
condition(req) => boolean返回 true 时才走缓存逻辑
varystring[][]Vary headers,不同值视为不同缓存条目
cacheControlbooleantrue是否设置 Cache-Control 响应头
tagsstring[][]缓存标签(用于 app.cache.invalidate(tag) 批量失效)
storestring'memory'存储适配器(Phase 1 仅支持 memory)

全局配置 (config.cache)

// src/config/default.ts
export default {
  cache: {
    enabled: true,       // 是否启用路由缓存(默认 true)
    defaultTtl: 60,      // 路由未指定 ttl 时的默认值(秒)
    maxEntries: 1000,    // LRU 最大缓存条目数
  },
};

缓存行为

响应头

说明
X-CacheHIT缓存命中
X-CacheMISS缓存未命中(首次请求或过期)
Cache-Controlpublic, max-age=NMISS 时 N=TTL,HIT 时 N=剩余秒数

缓存 Key 算法

默认 key 格式:${method}:${path}[?sortedQuery][|varyHeaders]

GET /products                              → GET:/products
GET /products?limit=10&page=2              → GET:/products?limit=10&page=2
GET /products (Accept-Language: zh-CN)     → GET:/products|accept-language=zh-CN
  • Query 参数自动排序(?b=2&a=1?a=1&b=2
  • 默认 key 不包含 Authorization / Cookie(安全设计)
  • 需要按用户区分缓存时,使用自定义 key 函数

不缓存的场景

  • 204 No Content 响应
  • 非 2xx 状态码(3xx / 4xx / 5xx)
  • cache: false 显式禁用
  • cache: 0 或负值
  • condition 返回 false
  • 自定义 key 返回空字符串

运行时 API

通过 app.cache 在路由 handler 中操作缓存:

// 按标签批量失效
app.post('/products', {}, async (req, res) => {
  await db.createProduct(req.body);
  await app.cache.invalidate('products'); // 所有带 products 标签的缓存全部失效
  res.json({ created: true }, 201);
});

// 删除指定 key
await app.cache.delete('GET:/products');

// 清空所有缓存
await app.cache.clear();

// 查看统计
const stats = app.cache.stats();
// → { entries: 42, hits: 128, misses: 31, hitRate: 0.805 }

Vary Headers

不同的请求头值会生成不同的缓存条目:

app.get('/products', {
  cache: {
    ttl: 120,
    vary: ['accept-language'],
  },
}, handler);
GET /products (Accept-Language: zh-CN) → 独立缓存
GET /products (Accept-Language: en-US) → 独立缓存

条件缓存

通过 condition 函数控制是否走缓存逻辑:

app.get('/data', {
  cache: {
    ttl: 60,
    // 带 refresh 参数时跳过缓存
    condition: (req) => !req.query.refresh,
  },
}, handler);
curl /data           # 走缓存
curl /data?refresh=1 # 跳过缓存,直接执行 handler

自定义 Key

需要按用户身份区分缓存时:

app.get('/profile', {
  cache: {
    ttl: 300,
    key: (req) => `profile:${req.headers['x-user-id'] ?? 'anonymous'}`,
  },
}, handler);

安全注意事项

:::warning 认证路由 + 缓存:默认缓存 key 不包含用户身份信息。如果同时使用 middlewares: ['auth']cache,不同用户可能命中相同缓存,导致数据泄露。

框架在启动时会检测此场景并发出警告。解决方案:

  • 使用自定义 key 函数包含用户标识
  • 使用 condition 排除已认证请求 :::
// ✅ 正确:自定义 key 包含用户 ID
app.get('/my-orders', {
  middlewares: ['auth'],
  cache: {
    ttl: 60,
    key: (req) => `orders:${req.headers['x-user-id']}`,
  },
}, handler);

// ✅ 正确:已认证用户不走缓存
app.get('/products', {
  middlewares: ['auth'],
  cache: {
    ttl: 60,
    condition: (req) => !req.headers.authorization,
  },
}, handler);