You need to enable JavaScript to run this app. Loopback 4使用Redis缓存数据库响应:类似Loopback 3的Redis缓存Mixin
优惠活动
大模型
产品
解决方案
定价
更多

Loopback 4使用Redis缓存数据库响应:类似Loopback 3的Redis缓存Mixin

LoopBack 4 基于Redis实现查询缓存方案(对齐LB3 Redis缓存Mixin效果)

1 前置依赖安装

首先安装所需依赖包:

npm install @loopback/redis ioredis lodash

2 配置Redis数据源

在src/datasources目录下新建redis.datasource.ts,配置Redis连接:

import {inject, lifeCycleObserver, LifeCycleObserver} from '@loopback/core';
import {juggler} from '@loopback/repository';
import * as Redis from 'ioredis';

const config = {
  name: 'redis',
  connector: 'redis',
  host: process.env.REDIS_HOST || '127.0.0.1',
  port: process.env.REDIS_PORT || 6379,
  password: process.env.REDIS_PASSWORD || '',
  db: process.env.REDIS_DB || 0,
};

@lifeCycleObserver('datasource')
export class RedisDataSource extends juggler.DataSource implements LifeCycleObserver {
  static dataSourceName = 'redis';
  static readonly defaultConfig = config;
  public redisClient: Redis.Redis;

  constructor(
    @inject('datasources.config.redis', {optional: true})
    dsConfig: object = config,
  ) {
    super(dsConfig);
    this.redisClient = new Redis(dsConfig);
  }
}

3 实现缓存装饰器与拦截器

LoopBack 4没有内置Mixin机制,我们通过AOP拦截器+方法装饰器的方式实现和LB3缓存Mixin一致的调用体验:

3.1 定义缓存装饰器

在src/decorators下新建cache.decorator.ts:

import {MethodDecoratorFactory} from '@loopback/core';

export const CACHEABLE_METADATA_KEY = 'cacheable';
export const CACHE_EVICT_METADATA_KEY = 'cache-evict';

// 查询缓存装饰器
export function Cacheable(opts?: {ttl?: number}) {
  return MethodDecoratorFactory.createDecorator(
    CACHEABLE_METADATA_KEY,
    {ttl: opts?.ttl || 300}, // 默认缓存5分钟
  );
}

// 清除缓存装饰器(用于新增/修改/删除方法)
export function CacheEvict(opts?: {pattern?: string}) {
  return MethodDecoratorFactory.createDecorator(
    CACHE_EVICT_METADATA_KEY,
    {pattern: opts?.pattern},
  );
}

3.2 实现缓存拦截器

在src/interceptors下新建cache.interceptor.ts:

import {inject, Interceptor, InvocationContext, Next, provider} from '@loopback/core';
import {RedisDataSource} from '../datasources';
import {CACHEABLE_METADATA_KEY, CACHE_EVICT_METADATA_KEY} from '../decorators/cache.decorator';
import * as crypto from 'crypto';
import * as _ from 'lodash';

@provider()
export class CacheInterceptor implements Interceptor {
  constructor(@inject('datasources.redis') private redisDs: RedisDataSource) {}

  async intercept(context: InvocationContext, next: Next) {
    const method = context.targetName + '.' + context.methodName;

    // 处理清除缓存逻辑
    const evictOpts = context.target?.constructor?.[CACHE_EVICT_METADATA_KEY]?.[context.methodName];
    if (evictOpts) {
      const pattern = evictOpts.pattern || `${context.targetName}:*`;
      const keys = await this.redisDs.redisClient.keys(pattern);
      if (keys.length) await this.redisDs.redisClient.del(keys);
      return next();
    }

    // 处理查询缓存逻辑
    const cacheOpts = context.target?.constructor?.[CACHEABLE_METADATA_KEY]?.[context.methodName];
    if (!cacheOpts) return next();

    // 生成缓存key:类名+方法名+参数哈希
    const argsHash = crypto.createHash('md5').update(JSON.stringify(context.args)).digest('hex');
    const cacheKey = `${context.targetName}:${context.methodName}:${argsHash}`;

    // 命中缓存直接返回
    const cached = await this.redisDs.redisClient.get(cacheKey);
    if (cached) return JSON.parse(cached);

    // 未命中则执行原方法,写入缓存
    const result = await next();
    await this.redisDs.redisClient.set(
      cacheKey,
      JSON.stringify(result),
      'EX',
      cacheOpts.ttl
    );
    return result;
  }
}

4 注册拦截器

在src/application.ts中注册全局拦截器:

import {CacheInterceptor} from './interceptors/cache.interceptor';

// 在应用构造函数中添加
this.interceptor(CacheInterceptor);

5 使用方式(和LB3 Mixin体验一致)

直接在Repository或Service的对应方法上添加装饰器即可:

import {repository} from '@loopback/repository';
import {UserRepository} from './user.repository';
import {Cacheable, CacheEvict} from '../decorators/cache.decorator';

export class UserService {
  constructor(
    @repository(UserRepository) private userRepo: UserRepository,
  ) {}

  // 查询方法加缓存,有效期10分钟
  @Cacheable({ttl: 600})
  async findUserById(id: string) {
    return this.userRepo.findById(id);
  }

  // 修改方法自动清除对应类下所有缓存
  @CacheEvict()
  async updateUser(id: string, data: Partial<User>) {
    return this.userRepo.updateById(id, data);
  }
}

注意事项

  • 复杂参数序列化时如果存在循环引用,需要提前处理或者替换序列化方案
  • 高并发场景下可增加缓存击穿防护(比如空值缓存、互斥锁)
  • 大结果集不建议直接缓存,可考虑拆分或者压缩存储

内容的提问来源于stack exchange,提问作者Miguta-K

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.20 21:36:07