@delon/cache缓存策略 delon-cache-caching-strategies

使用 @delon/cache 库实现缓存策略,支持内存缓存、LocalStorage 缓存、SessionStorage 缓存,具备TTL过期、缓存失效、分组命名空间等功能,优化性能减少API调用和数据库查询。

前端开发 0 次安装 0 次浏览 更新于 2/28/2026

@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值
  • [ ] 在数据变化时实现缓存失效
  • [ ] 使用描述性强,层次化的缓存键
  • [ ] 优雅地处理缓存未命中
  • [ ] 监控缓存命中率/未命中率
  • [ ] 考虑缓存大小限制
  • [ ] 测试缓存过期
  • [ ] 文档化缓存策略
  • [ ] 对关键数据实施缓存预热

参考资料