@delon/cache 缓存策略技能
这个技能帮助实现使用 @delon/cache 库的缓存。
核心原则
缓存类型
- 内存缓存:快速的内存缓存(页面刷新时丢失)
- LocalStorage 缓存:跨会话持久化
- SessionStorage 缓存:仅在会话中持久化
特性
- 基于TTL的过期
- 缓存失效(手动和自动)
- 缓存分组和命名空间
- 使用拦截器对HTTP请求进行缓存
- 支持异步数据的Observable
配置
// src/app/app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideDelonCache } from '@delon/cache';
export const appConfig: ApplicationConfig = {
providers: [
provideDelonCache({
mode: 'promise', // 'promise' | 'none'
request_method: 'POST',
meta_key: '__cache_meta',
prefix: '',
expire: 3600000 // 默认TTL:1小时(ms)
})
]
};
缓存服务
// src/app/core/services/cache.service.ts
import { Injectable, inject } from '@angular/core';
import { CacheService as DelonCacheService } from '@delon/cache';
import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class CacheService {
private cache = inject(DelonCacheService);
/**
* 设置缓存键值
*/
set<T>(key: string, value: T, options?: {
type?: 'memory' | 'localStorage' | 'sessionStorage';
expire?: number; // TTL以毫秒为单位
}): void {
this.cache.set(key, value, {
type: options?.type || 'memory',
expire: options?.expire || 3600000 // 1小时默认
});
}
/**
* 通过键获取缓存
*/
get<T>(key: string): T | null {
return this.cache.get<T>(key);
}
/**
* 检查缓存是否存在且未过期
*/
has(key: string): boolean {
return this.cache.has(key);
}
/**
* 通过键移除缓存
*/
remove(key: string): void {
this.cache.remove(key);
}
/**
* 清除所有缓存
*/
clear(): void {
this.cache.clear();
}
/**
* 获取或设置缓存(懒加载模式)
*/
getOrSet<T>(
key: string,
factory: () => Observable<T> | Promise<T>,
options?: {
type?: 'memory' | 'localStorage' | 'sessionStorage';
expire?: number;
}
): Observable<T> {
if (this.has(key)) {
return new Observable(observer => {
observer.next(this.get<T>(key)!);
observer.complete();
});
}
const result = factory();
if (result instanceof Observable) {
return new Observable(observer => {
result.subscribe({
next: (value) => {
this.set(key, value, options);
observer.next(value);
},
error: (err) => observer.error(err),
complete: () => observer.complete()
});
});
}
return new Observable(observer => {
result.then(value => {
this.set(key, value, options);
observer.next(value);
observer.complete();
}).catch(err => observer.error(err));
});
}
}
内存缓存(默认)
import { Component, inject, signal } from '@angular/core';
import { CacheService } from '@core/services/cache.service';
@Component({
selector: 'app-task-list',
template: `
<button nz-button (click)="loadTasks()">加载任务</button>
<button nz-button (click)="clearCache()">清除缓存</button>
@if (loading()) {
<nz-spin />
} @else {
@for (task of tasks(); track task.id) {
<div>{{ task.title }}</div>
}
}
`
})
export class TaskListComponent {
private cacheService = inject(CacheService);
private taskService = inject(TaskService);
loading = signal(false);
tasks = signal<Task[]>([]);
private readonly CACHE_KEY = 'tasks:list';
async loadTasks(): Promise<void> {
// 首先尝试从缓存中获取
const cached = this.cacheService.get<Task[]>(this.CACHE_KEY);
if (cached) {
console.log('从缓存中加载');
this.tasks.set(cached);
return;
}
// 从API加载
this.loading.set(true);
try {
const tasks = await this.taskService.getTasks();
// 缓存5分钟
this.cacheService.set(this.CACHE_KEY, tasks, {
type: 'memory',
expire: 5 * 60 * 1000 // 5分钟
});
this.tasks.set(tasks);
} finally {
this.loading.set(false);
}
}
clearCache(): void {
this.cacheService.remove(this.CACHE_KEY);
console.log('缓存已清除');
}
}
LocalStorage 缓存(持久化)
import { Injectable, inject } from '@angular/core';
import { CacheService } from '@core/services/cache.service';
@Injectable({ providedIn: 'root' })
export class UserPreferencesService {
private cacheService = inject(CacheService);
private readonly CACHE_KEY = 'user:preferences';
/**
* 保存用户偏好设置(跨会话持久化)
*/
savePreferences(preferences: UserPreferences): void {
this.cacheService.set(this.CACHE_KEY, preferences, {
type: 'localStorage',
expire: 30 * 24 * 60 * 60 * 1000 // 30天
});
}
/**
* 加载用户偏好设置
*/
loadPreferences(): UserPreferences | null {
return this.cacheService.get<UserPreferences>(this.CACHE_KEY);
}
/**
* 清除偏好设置
*/
clearPreferences(): void {
this.cacheService.remove(this.CACHE_KEY);
}
}
interface UserPreferences {
theme: 'light' | 'dark';
language: string;
sidebarCollapsed: boolean;
}
SessionStorage 缓存
/**
* 仅在当前会话中缓存搜索结果
*/
@Injectable({ providedIn: 'root' })
export class SearchService {
private cacheService = inject(CacheService);
async search(query: string): Promise<SearchResult[]> {
const cacheKey = `search:${query}`;
// 检查会话缓存
const cached = this.cacheService.get<SearchResult[]>(cacheKey);
if (cached) {
return cached;
}
// 执行搜索
const results = await this.performSearch(query);
// 仅在当前会话中缓存
this.cacheService.set(cacheKey, results, {
type: 'sessionStorage',
expire: 30 * 60 * 1000 // 30分钟
});
return results;
}
private async performSearch(query: string): Promise<SearchResult[]> {
// API调用
return [];
}
}
懒加载与getOrSet
@Injectable({ providedIn: 'root' })
export class ConfigService {
private cacheService = inject(CacheService);
private http = inject(HttpClient);
/**
* 自动缓存的加载配置
*/
loadConfig(): Observable<AppConfig> {
return this.cacheService.getOrSet(
'app:config',
() => this.http.get<AppConfig>('/api/config'),
{
type: 'localStorage',
expire: 24 * 60 * 60 * 1000 // 24小时
}
);
}
}
缓存失效
手动失效
@Injectable({ providedIn: 'root' })
export class TaskService {
private cacheService = inject(CacheService);
private taskRepository = inject(TaskRepository);
/**
* 创建任务并使缓存失效
*/
async createTask(task: Omit<Task, 'id'>): Promise<Task> {
const created = await this.taskRepository.create(task);
// 使任务列表缓存失效
this.cacheService.remove('tasks:list');
this.cacheService.remove(`tasks:blueprint:${task.blueprintId}`);
return created;
}
/**
* 更新任务并使缓存失效
*/
async updateTask(id: string, updates: Partial<Task>): Promise<Task> {
const updated = await this.taskRepository.update(id, updates);
// 使特定任务缓存失效
this.cacheService.remove(`tasks:${id}`);
// 使列表缓存失效
this.cacheService.remove('tasks:list');
return updated;
}
}
组缓存失效
@Injectable({ providedIn: 'root' })
export class CacheInvalidationService {
private cacheService = inject(CacheService);
/**
* 使所有带有前缀的缓存失效
*/
invalidateGroup(prefix: string): void {
// @delon/cache没有内置的组失效
// 所以我们手动跟踪缓存键
const keys = this.getCacheKeys(prefix);
keys.forEach(key => this.cacheService.remove(key));
}
private cacheKeys = new Set<string>();
registerCacheKey(key: string): void {
this.cacheKeys.add(key);
}
private getCacheKeys(prefix: string): string[] {
return Array.from(this.cacheKeys).filter(key => key.startsWith(prefix));
}
}
HTTP缓存拦截器
// src/app/core/interceptors/cache.interceptor.ts
import { HttpInterceptorFn, HttpResponse } from '@angular/common/http';
import { inject } from '@angular/core';
import { CacheService } from '@core/services/cache.service';
import { of, tap } from 'rxjs';
/**
* 缓存GET请求
*/
export const cacheInterceptor: HttpInterceptorFn = (req, next) => {
const cacheService = inject(CacheService);
// 只缓存GET请求
if (req.method !== 'GET') {
return next(req);
}
// 跳过某些URL的缓存
const skipCache = req.headers.has('X-Skip-Cache') ||
req.url.includes('/api/realtime');
if (skipCache) {
return next(req);
}
// 从URL+参数生成缓存键
const cacheKey = `http:${req.urlWithParams}`;
// 检查缓存
const cached = cacheService.get<HttpResponse<any>>(cacheKey);
if (cached) {
console.log('从缓存中提供:', cacheKey);
return of(cached);
}
// 发起请求并缓存响应
return next(req).pipe(
tap(event => {
if (event instanceof HttpResponse) {
cacheService.set(cacheKey, event, {
type: 'memory',
expire: 5 * 60 * 1000 // 5分钟
});
}
})
);
};
注册拦截器
// src/app/app.config.ts
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { cacheInterceptor } from '@core/interceptors/cache.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
withInterceptors([cacheInterceptor])
)
]
};
缓存命名空间
@Injectable({ providedIn: 'root' })
export class NamespacedCacheService {
private cacheService = inject(CacheService);
constructor(private namespace: string) {}
private getKey(key: string): string {
return `${this.namespace}:${key}`;
}
set<T>(key: string, value: T, options?: any): void {
this.cacheService.set(this.getKey(key), value, options);
}
get<T>(key: string): T | null {
return this.cacheService.get<T>(this.getKey(key));
}
remove(key: string): void {
this.cacheService.remove(this.getKey(key));
}
}
// 使用
@Injectable({ providedIn: 'root' })
export class TaskCacheService extends NamespacedCacheService {
constructor() {
super('tasks');
}
}
缓存模式
读穿缓存
async getTask(id: string): Promise<Task> {
const cacheKey = `tasks:${id}`;
// 尝试缓存
let task = this.cacheService.get<Task>(cacheKey);
if (task) {
return task;
}
// 从仓库加载
task = await this.taskRepository.findById(id);
// 缓存结果
if (task) {
this.cacheService.set(cacheKey, task, {
type: 'memory',
expire: 10 * 60 * 1000 // 10分钟
});
}
return task;
}
写穿缓存
async updateTask(id: string, updates: Partial<Task>): Promise<Task> {
// 在仓库中更新
const updated = await this.taskRepository.update(id, updates);
// 更新缓存
const cacheKey = `tasks:${id}`;
this.cacheService.set(cacheKey, updated, {
type: 'memory',
expire: 10 * 60 * 1000
});
return updated;
}
缓存旁路模式
async getTasks(blueprintId: string): Promise<Task[]> {
const cacheKey = `tasks:blueprint:${blueprintId}`;
// 检查缓存
if (this.cacheService.has(cacheKey)) {
return this.cacheService.get<Task[]>(cacheKey)!;
}
// 从仓库加载
const tasks = await this.taskRepository.findByBlueprintId(blueprintId);
// 填充缓存
this.cacheService.set(cacheKey, tasks, {
type: 'memory',
expire: 5 * 60 * 1000
});
return tasks;
}
最佳实践
缓存键命名
// 好的:描述性强,层次化的键
'users:123'
'tasks:blueprint:abc-123'
'config:app:theme'
// 差的:通用的,扁平的键
'user'
'data123'
'cache'
TTL指南
// 静态数据:1小时 - 1天
this.cacheService.set('config', data, { expire: 24 * 60 * 60 * 1000 });
// 动态数据:1-10分钟
this.cacheService.set('tasks', data, { expire: 5 * 60 * 1000 });
// 用户特定:会话持续时间
this.cacheService.set('user:prefs', data, { type: 'sessionStorage' });
// 持久化:30天
this.cacheService.set('settings', data, {
type: 'localStorage',
expire: 30 * 24 * 60 * 60 * 1000
});
检查表
当实现缓存时:
- [ ] 选择适当的缓存类型(内存/localStorage/sessionStorage)
- [ ] 设置合理的TTL值
- [ ] 在数据变化时实现缓存失效
- [ ] 使用描述性强,层次化的缓存键
- [ ] 优雅地处理缓存未命中
- [ ] 监控缓存命中率/未命中率
- [ ] 考虑缓存大小限制
- [ ] 测试缓存过期
- [ ] 文档化缓存策略
- [ ] 对关键数据实施缓存预热