Chuyển đến nội dung

API cache

Đường dẫn import: @sdcorejs/nestjs/services

Package cache cung cấp backend LRU trong process, backend Redis tùy chọn, service cache-aside bền bỉ và decorator/interceptor HTTP handler có scope bảo mật.

Các export

ExportLoạiMục đích
CacheBackendinterfaceContract async get, set, del, clear, size
MemoryCacheBackendclassLRU trong process có giới hạn với TTL theo từng entry
RedisCacheBackendclassBackend Redis serialize JSON với scan có scope theo prefix
InvalidRedisCacheKeyPrefixErrorclassLỗi namespace Redis không an toàn/bị thiếu
CacheServiceclassAPI backend bền bỉ hướng DI và cache-aside
CacheModuleclassModule provider global; forRoot(config?)
CacheConfig, MemoryCacheConfig, RedisCacheConfig, RedisCacheOptionstypeContract cấu hình backend
CacheBackendKindtype'memory' | 'redis'
CACHE_CONFIGvalueDI token cấu hình đã phân giải
Cached, CachedOptionsdecorator/typeĐánh dấu handler với cache scope bắt buộc
CACHED_METADATAvalueKhóa metadata của decorator
CacheScopetype'global' | 'tenant' | 'user'
CacheKeyValue, CacheKeyResolvertypeVật liệu business-key an toàn với JSON và selector
CacheHttpRequestDescriptorinterfaceĐầu vào key method/URL/path/params/query/body đã chuẩn hóa
CacheInterceptorclassThực thi hành vi @Cached; phải đăng ký rõ ràng
RequestCacheMiddlewareclassThêm requestCache: Map mới cho mỗi request

Cấu hình backend

ts
CacheModule.forRoot({
  backend: 'memory',
  ttl: 60,
  maxEntries: 1_000,
  operationTimeoutMs: 1_000,
});
ts
CacheModule.forRoot({
  backend: 'redis',
  ttl: 300,
  operationTimeoutMs: 1_000,
  fallbackToMemory: false,
  redis: {
    host: 'redis.internal.example',
    port: 6379,
    db: 2,
    keyPrefix: 'orders:cache:',
  },
});
OptionMặc địnhLưu ý
backend'memory'Redis yêu cầu option redis và runtime ioredis
ttl60 giây0 tắt expiration
maxEntries1000Chỉ memory; entry được chạm lâu nhất sẽ bị evict
fallbackToMemorytrueChỉ khi khởi tạo Redis thất bại; prefix không hợp lệ vẫn ném lỗi
operationTimeoutMs10000 tắt timeout
redis.keyPrefixbắt buộcKhông rỗng; không thể chứa *, ?, [, ] hoặc \

clear()size() của Redis dùng SCAN MATCH <keyPrefix>*; chúng không bao giờ gọi FLUSHDB. Dùng prefix riêng cho từng ứng dụng/môi trường. Giá trị phải có thể serialize JSON.

CacheService

ts
const order = await cache.load(
  `order:${orderId}`,
  () => repository.findOneByOrFail({ id: orderId }),
  120,
);

await cache.set('catalog:version', 42, 0);
await cache.del(`order:${orderId}`);
Thành viênSignature / hành vi
backendKind'memory' hoặc 'redis' đã phân giải, bao gồm fallback
get<T>(key) => Promise<T | undefined>; đọc đồng thời dùng chung một lời gọi backend
set<T>(key, value, ttlSec?) => Promise<void>
del(key) => Promise<void>
clear() => Promise<void>
size() => Promise<number>
load<T>(key, factory, ttlSec?) => Promise<T>; miss đồng thời dùng chung một factory

Lỗi và timeout backend được log rồi suy giảm thành miss/no-op/zero. Lỗi factory từ load không bị nuốt. Kết quả factory undefined không bao giờ được cache. Khóa single-flight chỉ theo key, vì vậy không dùng factory khác nhau cho cùng key.

Đăng ký CacheInterceptor

CacheModule đăng ký và export CacheInterceptor, nhưng chủ ý không áp dụng nó. Nếu không có một trong các đăng ký dưới đây, @Cached() chỉ là metadata và handler luôn chạy.

Theo controller hoặc handler:

ts
@UseInterceptors(CacheInterceptor)
@Controller('reports')
export class ReportsController {}

Global:

ts
@Module({
  providers: [
    { provide: APP_INTERCEPTOR, useExisting: CacheInterceptor },
  ],
})
export class AppModule {}

Scope và key của @Cached

ts
@Get(':id')
@Cached({
  scope: 'tenant',
  ttl: 120,
  keyResolver: ({ params, query }) => ({ id: params.id, locale: query.locale }),
})
detail() {}

CachedOptions.scope là bắt buộc:

ScopeContext bắt buộcVật liệu isolation
globalkhôngChỉ namespace global rõ ràng
tenanttenant không rỗngMọi trường context an toàn với JSON trừ request/response/token/user và userId
usertenantuserId không rỗngMọi trường context an toàn với JSON trừ request/response/token/user

Role và permission được sắp xếp trước khi hash. Các trường context tùy chỉnh và declaration-merged được bao gồm. Thiếu danh tính, giá trị vòng/không an toàn, transport không được hỗ trợ hoặc exception trong keyResolver sẽ bypass cache, không bao giờ tạo shared fallback key.

Key cuối cùng là <Class>.<method>:<scopeHash>:<businessHash>. Theo mặc định, vật liệu business là HTTP descriptor đã chuẩn hóa gồm method, URL, path, params, query và body. Header và object request/response Nest thô bị loại trừ. Resolver tùy chỉnh chỉ chọn suffix business và không thể bỏ namespace scope bắt buộc.

@Cached hỗ trợ handler request/response một giá trị. Nó dùng emission Observable đầu tiên; stream nhiều emission không được hỗ trợ, handler không emit gì fail với RxJS EmptyError, và undefined không được cache.

Global scope

Chỉ dùng scope: 'global' khi response giống hệt nhau cho mọi tenant, user, role và tập permission. Interceptor không thể suy ra dữ liệu ứng dụng có an toàn để chia sẻ global hay không.

Cache cục bộ theo request

RequestCacheMiddleware chỉ tạo map; hãy áp dụng nó qua NestModule.configure của bạn:

ts
configure(consumer: MiddlewareConsumer): void {
  consumer.apply(RequestCacheMiddleware).forRoutes('*');
}

Map này không thay thế kiểm tra authorization và biến mất sau request.

Phát hành theo giấy phép MIT.