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
相关产品推荐
相关产品推荐

