feat: implement Redis caching with locking and optimized product search in SalesService and update compression dependency
All checks were successful
Build (develop) / Promote (main) / build-and-push-deploy (push) Successful in 2m52s

This commit is contained in:
Luis Eduardo Estevao
2026-05-05 14:08:34 -03:00
parent 5c86459157
commit 8d52e5202a
3 changed files with 89 additions and 151 deletions

45
package-lock.json generated
View File

@@ -20,7 +20,7 @@
"@types/cache-manager": "^3.4.3", "@types/cache-manager": "^3.4.3",
"cache-manager": "^3.6.3", "cache-manager": "^3.6.3",
"cache-manager-redis-store": "^3.0.1", "cache-manager-redis-store": "^3.0.1",
"compression": "^1.7.4", "compression": "^1.8.1",
"crc": "^4.3.2", "crc": "^4.3.2",
"fs": "0.0.1-security", "fs": "0.0.1-security",
"guid-typescript": "^1.0.9", "guid-typescript": "^1.0.9",
@@ -4181,16 +4181,17 @@
} }
}, },
"node_modules/compression": { "node_modules/compression": {
"version": "1.7.4", "version": "1.8.1",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
"integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
"license": "MIT",
"dependencies": { "dependencies": {
"accepts": "~1.3.5", "bytes": "3.1.2",
"bytes": "3.0.0", "compressible": "~2.0.18",
"compressible": "~2.0.16",
"debug": "2.6.9", "debug": "2.6.9",
"on-headers": "~1.0.2", "negotiator": "~0.6.4",
"safe-buffer": "5.1.2", "on-headers": "~1.1.0",
"safe-buffer": "5.2.1",
"vary": "~1.1.2" "vary": "~1.1.2"
}, },
"engines": { "engines": {
@@ -4198,17 +4199,22 @@
} }
}, },
"node_modules/compression/node_modules/bytes": { "node_modules/compression/node_modules/bytes": {
"version": "3.0.0", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/compression/node_modules/safe-buffer": { "node_modules/compression/node_modules/negotiator": {
"version": "5.1.2", "version": "0.6.4",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
}, },
"node_modules/concat-map": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
@@ -9810,9 +9816,10 @@
} }
}, },
"node_modules/on-headers": { "node_modules/on-headers": {
"version": "1.0.2", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.8" "node": ">= 0.8"
} }

View File

@@ -32,7 +32,7 @@
"@types/cache-manager": "^3.4.3", "@types/cache-manager": "^3.4.3",
"cache-manager": "^3.6.3", "cache-manager": "^3.6.3",
"cache-manager-redis-store": "^3.0.1", "cache-manager-redis-store": "^3.0.1",
"compression": "^1.7.4", "compression": "^1.8.1",
"crc": "^4.3.2", "crc": "^4.3.2",
"fs": "0.0.1-security", "fs": "0.0.1-security",
"guid-typescript": "^1.0.9", "guid-typescript": "^1.0.9",

View File

@@ -27,8 +27,39 @@ export class SalesService {
async GetProducts2(store: string, pageSize: number, pageNumber: number, filter: FilterProduct = null,) { async GetProducts2(store: string, pageSize: number, pageNumber: number, filter: FilterProduct = null,) {
const connectionDb = new Connection(connectionOptions); const cacheKey = `GetProducts2:${store}:${pageSize}:${pageNumber}:${JSON.stringify(filter)}`;
await connectionDb.connect(); const lockKey = `lock:${cacheKey}`;
const lockTimeout = 30;
// Fase 1: Verificar cache Redis
try {
const cachedResult = await this.redisClient.get(cacheKey);
if (cachedResult) {
console.log('GetProducts2 - Retornando resultado do cache');
return JSON.parse(cachedResult);
}
} catch (err) {
console.error('Erro ao acessar o Redis no GetProducts2:', err?.message || err);
}
// Lock para evitar thundering herd
const lockValue = Date.now() + lockTimeout * 1000 + 1;
let acquiredLock: string | null = null;
try {
acquiredLock = await this.redisClient.set(lockKey, lockValue, 'NX', 'EX', lockTimeout);
} catch (err) {
console.error('Erro ao adquirir lock no Redis (GetProducts2):', err?.message || err);
acquiredLock = 'OK'; // Em caso de erro no Redis, prosseguir sem lock
}
if (acquiredLock !== 'OK') {
console.log('Lock não adquirido (GetProducts2), aguardando...');
await this.sleep(1000);
return this.GetProducts2(store, pageSize, pageNumber, filter);
}
// Fase 2: Reutilizar conexão via pool
const connectionDb = getConnection();
const queryRunner = connectionDb.createQueryRunner(); const queryRunner = connectionDb.createQueryRunner();
await queryRunner.connect(); await queryRunner.connect();
try { try {
@@ -38,55 +69,11 @@ export class SalesService {
pageNumber = 1; pageNumber = 1;
const offSet = (pageNumber - 1) * pageSize; const offSet = (pageNumber - 1) * pageSize;
const sqlProduto = `SELECT
esvlistaprodutos.CODPROD as "idProduct",
esvlistaprodutos.SEQ as "seq",
esvlistaprodutos.DESCRICAO as "smallDescription",
esvlistaprodutos.NOMEECOMMERCE as "title",
esvlistaprodutos.CODFAB as "idProvider",
esvlistaprodutos.CODAUXILIAR as "ean",
esvlistaprodutos.TIPOPRODUTO as "productType",
esvlistaprodutos.DADOSTECNICOS as "technicalData",
esvlistaprodutos.INFORMACOESTECNICAS as "description",
esvlistaprodutos.URLIMAGEM as "urlImage",
esvlistaprodutos.NOMEMARCA as "brand",
esvlistaprodutos.NOMEDEPARTAMENTO as "department",
esvlistaprodutos.NOMESECAO as "section",
esvlistaprodutos.NOMECATEGORIA as "category",
esvlistaprodutos.NOMEFORNECEDOR as "supplier",
esvlistaprodutos.CODIGOFILIAL as "store",
esvlistaprodutos.CLASSEVENDA as "saleAbc",
esvlistaprodutos.CLASSEESTOQUE as "stockAbc",
esvlistaprodutos.FORALINHA as "outLine",
esvlistaprodutos.PRECOVENDA as "listPrice",
esvlistaprodutos.PRECOPROMOCIONAL as "salePrice",
esvlistaprodutos.PRECOPROMOCIONAL as "salePromotion",
esvlistaprodutos.PRECOPROMOCIONAL_AVISTA as "salePromotion_cash",
esvlistaprodutos.PERCENTUALDESCONTO as "offPercent",
esvlistaprodutos.QTESTOQUE_DISPONIVEL as "stock",
esvlistaprodutos.QTCAIXAS as "boxStock",
esvlistaprodutos.ESTOQUE_DISP_LOJA as "store_stock",
esvlistaprodutos.ESTOQUE_DISP_CAIXA_LOJA as "store_boxStock",
esvlistaprodutos.MULTIPLO as "mutiple",
esvlistaprodutos.UNIDADE as "unity",
esvlistaprodutos.URLDEPARTAMENTO as "urlDepartment",
esvlistaprodutos.URLSECAO as "urlSection",
esvlistaprodutos.PRODUTO_COM_REDUCAO_PRECO as "downPrice",
esvlistaprodutos.PRODUTO_EM_CAMPANHA as "compaing",
esvlistaprodutos.BASETINTOMETRICO as "base",
esvlistaprodutos.LETRABASETINTOMETRICO as "letter",
esvlistaprodutos.LINHATINTOMETRICO as "line",
esvlistaprodutos.LITRAGEM as "can",
esvlistaprodutos.QUANTIDADE_ESTOQUE_GERAL as "full_stock",
esvlistaprodutos.TEM_PRODUTO_SIMILAR as "similar",
NVL(esvlistaprodutos.TIPO, 'SEM') as "type_campaing"
FROM ESVLISTAPRODUTOS
WHERE 1 = 1 `;
if (filter && filter.text.length > 0) { if (filter && filter.text.length > 0) {
const description = filter.text.toUpperCase(); const description = filter.text.toUpperCase();
console.log('consultando por codigo de fabrica'); console.log('consultando por texto: ' + description);
// Fase 3: Query unificada - busca por CODFAB OU DESCRICAO em uma única query
let products = await queryRunner.manager let products = await queryRunner.manager
.getRepository(SalesProduct) .getRepository(SalesProduct)
.createQueryBuilder('esvlistaprodutos') .createQueryBuilder('esvlistaprodutos')
@@ -97,8 +84,6 @@ export class SalesService {
.addSelect("\"esvlistaprodutos\".CODFAB", "idProvider") .addSelect("\"esvlistaprodutos\".CODFAB", "idProvider")
.addSelect("\"esvlistaprodutos\".CODAUXILIAR", "ean") .addSelect("\"esvlistaprodutos\".CODAUXILIAR", "ean")
.addSelect("\"esvlistaprodutos\".TIPOPRODUTO", "productType") .addSelect("\"esvlistaprodutos\".TIPOPRODUTO", "productType")
.addSelect("\"esvlistaprodutos\".DADOSTECNICOS", "technicalData")
.addSelect("\"esvlistaprodutos\".INFORMACOESTECNICAS", "description")
.addSelect("\"esvlistaprodutos\".URLIMAGEM", "urlImage") .addSelect("\"esvlistaprodutos\".URLIMAGEM", "urlImage")
.addSelect("\"esvlistaprodutos\".NOMEMARCA", "brand") .addSelect("\"esvlistaprodutos\".NOMEMARCA", "brand")
.addSelect("\"esvlistaprodutos\".NOMEDEPARTAMENTO", "department") .addSelect("\"esvlistaprodutos\".NOMEDEPARTAMENTO", "department")
@@ -131,13 +116,13 @@ export class SalesService {
.addSelect("\"esvlistaprodutos\".QUANTIDADE_ESTOQUE_GERAL", "full_stock") .addSelect("\"esvlistaprodutos\".QUANTIDADE_ESTOQUE_GERAL", "full_stock")
.addSelect("\"esvlistaprodutos\".TEM_PRODUTO_SIMILAR", "similar") .addSelect("\"esvlistaprodutos\".TEM_PRODUTO_SIMILAR", "similar")
.addSelect("\"esvlistaprodutos\".TIPO_CAMPANHA", "type_campaing") .addSelect("\"esvlistaprodutos\".TIPO_CAMPANHA", "type_campaing")
.where("UPPER(\"esvlistaprodutos\".CODFAB) LIKE '%'||REPLACE(:description, '@', '%')||'%'", { description }) .where("(UPPER(\"esvlistaprodutos\".CODFAB) LIKE '%'||REPLACE(:description, '@', '%')||'%' OR UPPER(\"esvlistaprodutos\".descricao) LIKE '%'||REPLACE(:description, '@', '%')||'%')", { description })
.andWhere("(\"esvlistaprodutos\".codfilial = :codfilial OR :codfilial = '99')", { codfilial: store }) .andWhere("(\"esvlistaprodutos\".codfilial = :codfilial OR :codfilial = '99')", { codfilial: store })
.andWhere("(\"esvlistaprodutos\".produto_com_reducao_preco = :produtoComReducaoPreco OR :produtoComReducaoPreco = 'N')", .andWhere("(\"esvlistaprodutos\".produto_com_reducao_preco = :produtoComReducaoPreco OR :produtoComReducaoPreco = 'N')",
{ produtoComReducaoPreco: (filter.markdown) ? 'S' : 'N' }) { produtoComReducaoPreco: (filter.markdown) ? 'S' : 'N' })
.andWhere("(\"esvlistaprodutos\".produto_em_campanha = :campaign OR :campaign = 'N')", .andWhere("(\"esvlistaprodutos\".produto_em_campanha = :campaign OR :campaign = 'N')",
{ campaign: (filter.campaign) ? 'I' : 'N' }) { campaign: (filter.campaign) ? 'I' : 'N' })
.andWhere("(\"esvlistaprodutos\".produto_em_campanha = :oportunity OR :oportunity = 'N')", .andWhere("(\"esvlistaprodutos\".produto_oportunidade = :oportunity OR :oportunity = 'N')",
{ oportunity: (filter.oportunity) ? 'O' : 'N' }) { oportunity: (filter.oportunity) ? 'O' : 'N' })
.andWhere("(\"esvlistaprodutos\".produto_em_promocao = :produtoEmPromocao OR :produtoEmPromocao = 'N')", .andWhere("(\"esvlistaprodutos\".produto_em_promocao = :produtoEmPromocao OR :produtoEmPromocao = 'N')",
{ produtoEmPromocao: (filter.promotion) ? 'S' : 'N' }) { produtoEmPromocao: (filter.promotion) ? 'S' : 'N' })
@@ -145,69 +130,9 @@ export class SalesService {
.offset(offSet) .offset(offSet)
.orderBy("\"esvlistaprodutos\".DESCRICAO", "ASC") .orderBy("\"esvlistaprodutos\".DESCRICAO", "ASC")
.getRawMany(); .getRawMany();
if (products.length == 0) {
products = await queryRunner.manager
.getRepository(SalesProduct)
.createQueryBuilder('esvlistaprodutos')
.select("\"esvlistaprodutos\".CODPROD", "idProduct")
.addSelect("\"esvlistaprodutos\".SEQ", "seq")
.addSelect("\"esvlistaprodutos\".DESCRICAO", "smallDescription")
.addSelect("\"esvlistaprodutos\".NOMEECOMMERCE", "title")
.addSelect("\"esvlistaprodutos\".CODFAB", "idProvider")
.addSelect("\"esvlistaprodutos\".CODAUXILIAR", "ean")
.addSelect("\"esvlistaprodutos\".TIPOPRODUTO", "productType")
.addSelect("\"esvlistaprodutos\".DADOSTECNICOS", "technicalData")
.addSelect("\"esvlistaprodutos\".INFORMACOESTECNICAS", "description")
.addSelect("\"esvlistaprodutos\".URLIMAGEM", "urlImage")
.addSelect("\"esvlistaprodutos\".NOMEMARCA", "brand")
.addSelect("\"esvlistaprodutos\".NOMEDEPARTAMENTO", "department")
.addSelect("\"esvlistaprodutos\".NOMESECAO", "section")
.addSelect("\"esvlistaprodutos\".NOMECATEGORIA", "category")
.addSelect("\"esvlistaprodutos\".NOMEFORNECEDOR", "supplier")
.addSelect("\"esvlistaprodutos\".CODIGOFILIAL", "store")
.addSelect("\"esvlistaprodutos\".CLASSEVENDA", "saleAbc")
.addSelect("\"esvlistaprodutos\".CLASSEESTOQUE", "stockAbc")
.addSelect("\"esvlistaprodutos\".FORALINHA", "outLine")
.addSelect("\"esvlistaprodutos\".PRECOVENDA", "listPrice")
.addSelect("\"esvlistaprodutos\".PRECOPROMOCIONAL", "salePrice")
.addSelect("\"esvlistaprodutos\".PRECOPROMOCIONAL", "salePromotion")
.addSelect("\"esvlistaprodutos\".PRECOPROMOCIONAL_AVISTA", "salePromotion_cash")
.addSelect("\"esvlistaprodutos\".PERCENTUALDESCONTO", "offPercent")
.addSelect("\"esvlistaprodutos\".QTESTOQUE_DISPONIVEL", "stock")
.addSelect("\"esvlistaprodutos\".QTCAIXAS", "boxStock")
.addSelect("\"esvlistaprodutos\".ESTOQUE_DISP_LOJA", "store_stock")
.addSelect("\"esvlistaprodutos\".ESTOQUE_DISP_CAIXA_LOJA", "store_boxStock")
.addSelect("\"esvlistaprodutos\".MULTIPLO", "mutiple")
.addSelect("\"esvlistaprodutos\".UNIDADE", "unity")
.addSelect("\"esvlistaprodutos\".URLDEPARTAMENTO", "urlDepartment")
.addSelect("\"esvlistaprodutos\".URLSECAO", "urlSection")
.addSelect("\"esvlistaprodutos\".PRODUTO_COM_REDUCAO_PRECO", "downPrice")
.addSelect("\"esvlistaprodutos\".PRODUTO_EM_CAMPANHA", "compaing")
.addSelect("\"esvlistaprodutos\".BASETINTOMETRICO", "base")
.addSelect("\"esvlistaprodutos\".LETRABASETINTOMETRICO", "letter")
.addSelect("\"esvlistaprodutos\".LINHATINTOMETRICO", "line")
.addSelect("\"esvlistaprodutos\".LITRAGEM", "can")
.addSelect("\"esvlistaprodutos\".QUANTIDADE_ESTOQUE_GERAL", "full_stock")
.addSelect("\"esvlistaprodutos\".TEM_PRODUTO_SIMILAR", "similar")
.addSelect("\"esvlistaprodutos\".TIPO_CAMPANHA", "type_campaing")
.where("UPPER(\"esvlistaprodutos\".descricao) LIKE '%'||REPLACE(:description, '@', '%')||'%'", { description })
.andWhere("(\"esvlistaprodutos\".codfilial = :codfilial OR :codfilial = '99')", { codfilial: store })
.andWhere("(\"esvlistaprodutos\".produto_com_reducao_preco = :produtoComReducaoPreco OR :produtoComReducaoPreco = 'N')",
{ produtoComReducaoPreco: (filter.markdown) ? 'S' : 'N' })
.andWhere("(\"esvlistaprodutos\".produto_em_campanha = :campaign OR :campaign = 'N')",
{ campaign: (filter.campaign) ? 'I' : 'N' })
.andWhere("(\"esvlistaprodutos\".produto_oportunidade = :oportunity OR :oportunity = 'N')",
{ oportunity: (filter.oportunity) ? 'O' : 'N' })
.andWhere("(\"esvlistaprodutos\".produto_em_promocao = :produtoEmPromocao OR :produtoEmPromocao = 'N')",
{ produtoEmPromocao: (filter.promotion) ? 'S' : 'N' })
.orderBy("\"esvlistaprodutos\".CLASSEVENDA")
.limit(pageSize)
.offset(offSet)
.orderBy("\"esvlistaprodutos\".DESCRICAO", "ASC")
.getRawMany();
}
products = this.createListImages(products); products = this.createListImages(products);
console.log(products.length + ' produtos'); console.log(products.length + ' produtos');
try { await this.redisClient.set(cacheKey, JSON.stringify(products), 'EX', 900); } catch (cacheErr) { console.error('Erro ao salvar cache GetProducts2:', cacheErr?.message || cacheErr); }
return products; return products;
} }
@@ -234,8 +159,6 @@ export class SalesService {
.addSelect("\"esvlistaprodutos\".CODFAB", "idProvider") .addSelect("\"esvlistaprodutos\".CODFAB", "idProvider")
.addSelect("\"esvlistaprodutos\".CODAUXILIAR", "ean") .addSelect("\"esvlistaprodutos\".CODAUXILIAR", "ean")
.addSelect("\"esvlistaprodutos\".TIPOPRODUTO", "productType") .addSelect("\"esvlistaprodutos\".TIPOPRODUTO", "productType")
.addSelect("\"esvlistaprodutos\".DADOSTECNICOS", "technicalData")
.addSelect("\"esvlistaprodutos\".INFORMACOESTECNICAS", "description")
.addSelect("\"esvlistaprodutos\".URLIMAGEM", "urlImage") .addSelect("\"esvlistaprodutos\".URLIMAGEM", "urlImage")
.addSelect("\"esvlistaprodutos\".NOMEMARCA", "brand") .addSelect("\"esvlistaprodutos\".NOMEMARCA", "brand")
.addSelect("\"esvlistaprodutos\".NOMEDEPARTAMENTO", "department") .addSelect("\"esvlistaprodutos\".NOMEDEPARTAMENTO", "department")
@@ -285,6 +208,7 @@ export class SalesService {
.getRawMany(); .getRawMany();
products = this.createListImages(products); products = this.createListImages(products);
console.log(products.length + ' produtos'); console.log(products.length + ' produtos');
try { await this.redisClient.set(cacheKey, JSON.stringify(products), 'EX', 900); } catch (cacheErr) { console.error('Erro ao salvar cache GetProducts2:', cacheErr?.message || cacheErr); }
return products; return products;
} else { } else {
@@ -300,8 +224,6 @@ export class SalesService {
.addSelect("\"esvlistaprodutos\".CODFAB", "idProvider") .addSelect("\"esvlistaprodutos\".CODFAB", "idProvider")
.addSelect("\"esvlistaprodutos\".CODAUXILIAR", "ean") .addSelect("\"esvlistaprodutos\".CODAUXILIAR", "ean")
.addSelect("\"esvlistaprodutos\".TIPOPRODUTO", "productType") .addSelect("\"esvlistaprodutos\".TIPOPRODUTO", "productType")
.addSelect("\"esvlistaprodutos\".DADOSTECNICOS", "technicalData")
.addSelect("\"esvlistaprodutos\".INFORMACOESTECNICAS", "description")
.addSelect("\"esvlistaprodutos\".URLIMAGEM", "urlImage") .addSelect("\"esvlistaprodutos\".URLIMAGEM", "urlImage")
.addSelect("\"esvlistaprodutos\".NOMEMARCA", "brand") .addSelect("\"esvlistaprodutos\".NOMEMARCA", "brand")
.addSelect("\"esvlistaprodutos\".NOMEDEPARTAMENTO", "department") .addSelect("\"esvlistaprodutos\".NOMEDEPARTAMENTO", "department")
@@ -349,6 +271,7 @@ export class SalesService {
.getRawMany(); .getRawMany();
products = this.createListImages(products); products = this.createListImages(products);
console.log(products.length + ' produtos'); console.log(products.length + ' produtos');
try { await this.redisClient.set(cacheKey, JSON.stringify(products), 'EX', 900); } catch (cacheErr) { console.error('Erro ao salvar cache GetProducts2:', cacheErr?.message || cacheErr); }
return products; return products;
} }
@@ -357,7 +280,15 @@ export class SalesService {
throw error; throw error;
} finally { } finally {
await queryRunner.release(); await queryRunner.release();
await connectionDb.close(); // Não fecha a conexão - reutilizada pelo pool
try {
const currentLockValue = await this.redisClient.get(lockKey);
if (currentLockValue === lockValue.toString()) {
await this.redisClient.del(lockKey);
}
} catch (lockErr) {
console.error('Erro ao liberar lock Redis (GetProducts2):', lockErr?.message || lockErr);
}
} }
} }
@@ -1114,17 +1045,18 @@ export class SalesService {
} }
createListImages(products: SalesProduct[]): SalesProduct[] { createListImages(products: SalesProduct[]): SalesProduct[] {
products.forEach(product => { for (let i = 0; i < products.length; i++) {
const product = products[i];
product.images = []; product.images = [];
if (product.urlImage != null) { if (product.urlImage != null && product.urlImage.length > 0) {
let urlImage = product.urlImage; product.images = product.urlImage
if (urlImage.indexOf(";") == -1) { .split(';')
urlImage += ";"; .map(img => img.trim())
} .filter(img => img.length > 0);
const images = product.urlImage.split(";");
product.images = images.map(i => i.trim());
} }
}); // Adiciona thumbnail para acesso rápido no frontend
(product as any).thumbnail = product.images.length > 0 ? product.images[0] : null;
}
return products; return products;
} }
@@ -1415,8 +1347,7 @@ export class SalesService {
const skipReg = ((pageNumber - 1) * pageSize); const skipReg = ((pageNumber - 1) * pageSize);
const pagination = ` OFFSET ${skipReg} ROWS FETCH NEXT ${pageSize} ROWS ONLY`; const pagination = ` OFFSET ${skipReg} ROWS FETCH NEXT ${pageSize} ROWS ONLY`;
const connectionDb = new Connection(connectionOptions); const connectionDb = getConnection();
await connectionDb.connect();
const queryRunner = connectionDb.createQueryRunner(); const queryRunner = connectionDb.createQueryRunner();
await queryRunner.connect(); await queryRunner.connect();
@@ -1425,7 +1356,7 @@ export class SalesService {
products = this.createListImages(products); products = this.createListImages(products);
console.log("Total de produtos: " + products.length); console.log("Total de produtos: " + products.length);
await this.redisClient.set(cacheKey, JSON.stringify(products), 'EX', 3600); await this.redisClient.set(cacheKey, JSON.stringify(products), 'EX', 900);
return products; return products;
} catch (err) { } catch (err) {
@@ -1433,7 +1364,7 @@ export class SalesService {
throw err; throw err;
} finally { } finally {
await queryRunner.release(); await queryRunner.release();
await connectionDb.close(); // Não fecha a conexão - reutilizada pelo pool
} }
} }