62b659246f
- Added Redis caching to BusinessActivitiesService for findAll and findOne methods. - Integrated Redis caching in BusinessActivityComplexesService for findAll and findOne methods. - Enhanced ConsumersService with Redis caching for findAll and findOne methods. - Introduced cache invalidation for partner consumers and business activities. - Created RedisKeyMaker utility for generating cache keys for consumers, partners, and POS. - Implemented cache invalidation services for partners and POS. - Added Redis service methods for JSON handling and key deletion by patterns. - Updated goods service to include caching and invalidation for goods list. - Introduced DTO for updating goods.
134 lines
3.2 KiB
TypeScript
134 lines
3.2 KiB
TypeScript
import { Injectable, Logger, OnModuleDestroy } from '@nestjs/common'
|
|
import Redis from 'ioredis'
|
|
|
|
@Injectable()
|
|
export class RedisService implements OnModuleDestroy {
|
|
private readonly logger = new Logger(RedisService.name)
|
|
private readonly client: Redis
|
|
|
|
constructor() {
|
|
const host = process.env.REDIS_HOST || 'redis'
|
|
const port = Number(process.env.REDIS_PORT || 6379)
|
|
const password = process.env.REDIS_PASSWORD || undefined
|
|
const db = Number(process.env.REDIS_DB || 0)
|
|
|
|
this.client = new Redis({
|
|
host,
|
|
port,
|
|
password,
|
|
db,
|
|
lazyConnect: true,
|
|
maxRetriesPerRequest: 3,
|
|
})
|
|
|
|
this.client.on('error', (error) => {
|
|
this.logger.error(`Redis error: ${error.message}`)
|
|
})
|
|
}
|
|
|
|
async getClient(): Promise<Redis> {
|
|
if (this.client.status !== 'ready') {
|
|
await this.client.connect()
|
|
}
|
|
|
|
return this.client
|
|
}
|
|
|
|
async get(key: string): Promise<string | null> {
|
|
const client = await this.getClient()
|
|
return client.get(key)
|
|
}
|
|
|
|
async getJson<T>(key: string): Promise<T | null> {
|
|
const value = await this.get(key)
|
|
if (!value) {
|
|
return null
|
|
}
|
|
|
|
try {
|
|
return JSON.parse(value) as T
|
|
} catch {
|
|
this.logger.warn(`Invalid JSON for key "${key}"`)
|
|
return null
|
|
}
|
|
}
|
|
|
|
async set(
|
|
key: string,
|
|
value: string,
|
|
ttlSeconds?: number,
|
|
): Promise<'OK' | null> {
|
|
const client = await this.getClient()
|
|
if (ttlSeconds && ttlSeconds > 0) {
|
|
return client.set(key, value, 'EX', ttlSeconds)
|
|
}
|
|
return client.set(key, value)
|
|
}
|
|
|
|
async setJson(
|
|
key: string,
|
|
value: unknown,
|
|
ttlSeconds?: number,
|
|
): Promise<'OK' | null> {
|
|
return this.set(key, JSON.stringify(value), ttlSeconds)
|
|
}
|
|
|
|
async delete(key: string): Promise<number> {
|
|
const client = await this.getClient()
|
|
return client.del(key)
|
|
}
|
|
|
|
async exists(key: string): Promise<boolean> {
|
|
const client = await this.getClient()
|
|
const result = await client.exists(key)
|
|
return result === 1
|
|
}
|
|
|
|
async expire(key: string, ttlSeconds: number): Promise<boolean> {
|
|
const client = await this.getClient()
|
|
const result = await client.expire(key, ttlSeconds)
|
|
return result === 1
|
|
}
|
|
|
|
async deleteByPattern(pattern: string): Promise<number> {
|
|
const client = await this.getClient()
|
|
let deletedCount = 0
|
|
let cursor = '0'
|
|
|
|
do {
|
|
const [nextCursor, keys] = await client.scan(
|
|
cursor,
|
|
'MATCH',
|
|
pattern,
|
|
'COUNT',
|
|
100,
|
|
)
|
|
cursor = nextCursor
|
|
|
|
if (keys.length > 0) {
|
|
const pipeline = client.pipeline()
|
|
keys.forEach(k => pipeline.del(k))
|
|
const results = await pipeline.exec()
|
|
if (results) {
|
|
deletedCount += results.reduce((sum, [, value]) => {
|
|
return sum + (typeof value === 'number' ? value : 0)
|
|
}, 0)
|
|
}
|
|
}
|
|
} while (cursor !== '0')
|
|
|
|
return deletedCount
|
|
}
|
|
|
|
async deleteByPatterns(patterns: string[]): Promise<number> {
|
|
const deletedCounts = await Promise.all(
|
|
patterns.map(pattern => this.deleteByPattern(pattern)),
|
|
)
|
|
return deletedCounts.reduce((sum, count) => sum + count, 0)
|
|
}
|
|
|
|
async onModuleDestroy() {
|
|
await this.client.quit()
|
|
}
|
|
}
|